#include "common/pch.h"

#include <format>

#include <cftc/reflect/class.h>
#include <cftc/reflect/field.h>
#include <cftc/util/listentry.h>
#include <cftf/mysql/dbpool.h>
#include <cftf/loger/syslog.h>

#include "object/player.h"
#include "object/updatefields.h"
#include "object/item.h"
#include "object/formulas.h"
#include "object/transport.h"
#include "object/bag.h"
#include "world/world.h"
#include "map/mapmgr.h"
#include "cs/cs_message.h"
#include "metadata/player_create_info.h"
#include "metadata/player_xp_for_level.h"
#include "metadata/item.h"

// corpse reclaim times
#define DEATH_EXPIRE_STEP (5*MINUTE)
#define MAX_DEATH_COUNT 3

static const unsigned int corpseReclaimDelay[MAX_DEATH_COUNT] = { 30, 60, 120 };

#define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS)

#define PLAYER_SKILL_INDEX(x)       (PLAYER_SKILL_INFO_1_1 + ((x)*3))
#define PLAYER_SKILL_VALUE_INDEX(x) (PLAYER_SKILL_INDEX(x)+1)
#define PLAYER_SKILL_BONUS_INDEX(x) (PLAYER_SKILL_INDEX(x)+2)

#define SKILL_VALUE(x)         PAIR32_LOPART(x)
#define SKILL_MAX(x)           PAIR32_HIPART(x)
#define MAKE_SKILL_VALUE(v, m) MAKE_PAIR32(v,m)

#define SKILL_TEMP_BONUS(x)    short(PAIR32_LOPART(x))
#define SKILL_PERM_BONUS(x)    short(PAIR32_HIPART(x))
#define MAKE_SKILL_BONUS(t, p) MAKE_PAIR32(t,p)

namespace battle::object
{

	Player::Player()
	{
		player_map_entry_ = std::make_shared<decltype(player_map_entry_)::element_type>(this);
	}

	bool Player::LoadFromDB(ObjectGuid guid, const std::vector<std::shared_ptr<cftf::mysql::DataSet>>& datasets)
	{
        auto result = datasets.at(PLAYER_LOGIN_QUERY_LOADFROM);

        if (!result)
        {
            cftf::loger::SysLog::Instance().Error(std::format("{} not found in table `characters`, can't load. ", guid.GetString().c_str()));
            return false;
        }

        unsigned int dbAccountId = result->GetByFieldIdx(1)->Get<unsigned int>();

#if 0
        // check if the character's account in the db and the logged in account match.
        // player should be able to load/delete character only with correct account!
        if (dbAccountId != GetSession()->GetAccountId())
        {
            sLog.outError("%s loading from wrong account (is: %u, should be: %u)",
                guid.GetString().c_str(), GetSession()->GetAccountId(), dbAccountId);
            delete result;
            return false;
        }
#endif

        Object::_Create(guid.GetCounter(), 0, HIGHGUID_PLAYER);

        m_name = result->GetByFieldIdx(2)->Get<std::string>();

#if 0
        // check name limitations
        if (ObjectMgr::CheckPlayerName(m_name) != CHAR_NAME_SUCCESS ||
            (GetSession()->GetSecurity() == SEC_PLAYER && sObjectMgr.IsReservedName(m_name)))
        {
            delete result;
            CharacterDatabase.PExecute("UPDATE `characters` SET `at_login` = `at_login` | '%u' WHERE `guid` ='%u'",
                uint32(AT_LOGIN_RENAME), guid.GetCounter());
            return false;
        }
#endif


        // overwrite possible wrong/corrupted guid
        SetGuidValue(OBJECT_FIELD_GUID, guid);

        // overwrite some data fields
        SetByteValue(UNIT_FIELD_BYTES_0, 0, result->GetByFieldIdx(3)->Get<unsigned char>()); // race
        SetByteValue(UNIT_FIELD_BYTES_0, 1, result->GetByFieldIdx(4)->Get<unsigned char>()); // class

        unsigned char gender = result->GetByFieldIdx(5)->Get<unsigned char>() & 0x01;             // allowed only 1 bit values male/female cases (for fit drunk gender part)
        SetByteValue(UNIT_FIELD_BYTES_0, 2, gender);            // gender

        SetByteValue(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_UNK3 | UNIT_BYTE2_FLAG_UNK5);

#if 0
        // check if race/class combination is valid
        PlayerInfo const* info = sObjectMgr.GetPlayerInfo(getRace(), getClass());
        if (!info)
        {
            DEBUG_FILTER_LOG(LOG_FILTER_PLAYER_STATS, "Player (GUID: %u) has wrong race/class (%u/%u), can't be loaded.",
                guid.GetCounter(), getRace(), getClass());
            return false;
        }
#endif

        SetUint32Value((unsigned short)UNIT_FIELD_LEVEL, result->GetByFieldIdx(6)->Get<unsigned int>());
        SetUint32Value(PLAYER_XP, result->GetByFieldIdx(7)->Get<unsigned int>());

        _LoadIntoDataField(result->GetByFieldIdx(51)->Get<std::string>().c_str(), PLAYER_EXPLORED_ZONES_1, PLAYER_EXPLORED_ZONES_SIZE);

        InitDisplayIds();                                       // model, scale and model data
        unsigned int money = result->GetByFieldIdx(8)->Get<unsigned int>();
        if (money > MAX_MONEY_AMOUNT)
        {
            money = MAX_MONEY_AMOUNT;
        }
        SetMoney(money);

        SetUint32Value(PLAYER_BYTES, result->GetByFieldIdx(9)->Get<unsigned int>());
        SetUint32Value(PLAYER_BYTES_2, result->GetByFieldIdx(10)->Get<unsigned int>());

        m_drunk = result->GetByFieldIdx(44)->Get<unsigned short>();

        SetUInt16Value(PLAYER_BYTES_3, 0, (m_drunk & 0xFFFE) | gender);

        SetUint32Value(PLAYER_FLAGS, result->GetByFieldIdx(11)->Get<unsigned int>());
        SetInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, result->GetByFieldIdx(43)->Get<int>());

        SetUint32Value(PLAYER_AMMO_ID, result->GetByFieldIdx(53)->Get<unsigned int>());

        // Action bars state
        SetByteValue(PLAYER_FIELD_BYTES, 2, result->GetByFieldIdx(54)->Get<unsigned char>());

        // cleanup inventory related item value fields (its will be filled correctly in _LoadInventory)
        for (unsigned char slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
        {
            SetGuidValue(PLAYER_FIELD_INV_SLOT_HEAD + (slot * 2), ObjectGuid());
            SetVisibleItemSlot(slot, NULL);

            delete m_items[slot];
            m_items[slot] = NULL;
        }

        cftf::loger::SysLog::Instance().Error(std::format("Load Basic value of player {} is: ", m_name.c_str()));
        outDebugStatsValues();

        // Need to call it to initialize m_team (m_team can be calculated from race)
        // Other way is to saves m_team into characters table.
        setFactionForRace(getRace());
        SetCharm(nullptr);

        // load home bind and check in same time class/race pair, it used later for restore broken positions
        if (!_LoadHomeBind(datasets.at(PLAYER_LOGIN_QUERY_LOADHOMEBIND)))
        {
            return false;
        }
        InitPrimaryProfessions();                               // to max set before any spell loaded

        // init saved position, and fix it later if problematic
        unsigned int transGUID = result->GetByFieldIdx(30)->Get<unsigned int>();
        Relocate(result->GetByFieldIdx(12)->Get<float>(), result->GetByFieldIdx(13)->Get<float>(), result->GetByFieldIdx(14)->Get<float>(), result->GetByFieldIdx(16)->Get<float>());
        SetLocationMapId(result->GetByFieldIdx(15)->Get<unsigned int>());
#if 0
        _LoadGroup(datasets.at(PLAYER_LOGIN_QUERY_LOADGROUP));
#endif
        m_highest_rank.rank = result->GetByFieldIdx(38)->Get<unsigned int>();
        m_highest_rank = battle::object::formula::Honor::CalculateRankInfo(m_highest_rank);
        m_standing_pos = result->GetByFieldIdx(39)->Get<unsigned int>();
        m_stored_honor = result->GetByFieldIdx(40)->Get<float>();
        m_stored_dishonorableKills = result->GetByFieldIdx(41)->Get<unsigned int>();
        m_stored_honorableKills = result->GetByFieldIdx(42)->Get<unsigned int>();

        _LoadHonorCP(datasets.at(PLAYER_LOGIN_QUERY_LOADHONORCP));

        _LoadBoundInstances(datasets.at(PLAYER_LOGIN_QUERY_LOADBOUNDINSTANCES));

        if (!IsPositionValid())
        {
            cftf::loger::SysLog::Instance().Error(std::format("{} have invalid coordinates (X: {} Y: {} Z: {} O: {}). Teleport to default race/class locations.",
                guid.GetString().c_str(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation()));
            RelocateToHomebind();

            transGUID = 0;

            m_movementInfo.ClearTransportData();
        }

        _LoadBGData(datasets.at(PLAYER_LOGIN_QUERY_LOADBGDATA));
#if 0
        if (m_bgData.bgInstanceID)                              // saved in BattleGround
        {
            BattleGround* currentBg = sBattleGroundMgr.GetBattleGround(m_bgData.bgInstanceID, BATTLEGROUND_TYPE_NONE);

            bool player_at_bg = currentBg && currentBg->IsPlayerInBattleGround(GetObjectGuid());

            if (player_at_bg && currentBg->GetStatus() != STATUS_WAIT_LEAVE)
            {
                BattleGroundQueueTypeId bgQueueTypeId = sBattleGroundMgr.BGQueueTypeId(currentBg->GetTypeID());
                AddBattleGroundQueueId(bgQueueTypeId);

                m_bgData.bgTypeID = currentBg->GetTypeID();     // bg data not marked as modified

                // join player to battleground group
                currentBg->EventPlayerLoggedIn(this);
                currentBg->AddOrSetPlayerToCorrectBgGroup(this, GetObjectGuid(), m_bgData.bgTeam);

                SetInviteForBattleGroundQueueType(bgQueueTypeId, currentBg->GetInstanceID());
            }
            else
            {
                // leave bg
                if (player_at_bg)
                {
                    currentBg->RemovePlayerAtLeave(GetObjectGuid(), false, true);
                }

                // move to bg enter point
                const WorldLocation& _loc = GetBattleGroundEntryPoint();
                SetLocationMapId(_loc.mapid);
                Relocate(_loc.coord_x, _loc.coord_y, _loc.coord_z, _loc.orientation);

                // We are not in BG anymore
                SetBattleGroundId(0, BATTLEGROUND_TYPE_NONE);
                // remove outdated DB data in DB
                _SaveBGData();
            }
        }
        else
        {
            MapEntry const* mapEntry = sMapStore.LookupEntry(GetMapId());
            // if server restart after player save in BG or area
            // player can have current coordinates in to BG map, fix this
            if (!mapEntry || mapEntry->IsBattleGround())
            {
                const WorldLocation& _loc = GetBattleGroundEntryPoint();
                SetLocationMapId(_loc.mapid);
                Relocate(_loc.coord_x, _loc.coord_y, _loc.coord_z, _loc.orientation);

                // We are not in BG anymore
                SetBattleGroundId(0, BATTLEGROUND_TYPE_NONE);
                // remove outdated DB data in DB
                _SaveBGData();
            }
        }
#endif
        if (transGUID != 0)
        {
            m_movementInfo.SetTransportData(ObjectGuid(HIGHGUID_MO_TRANSPORT, transGUID), 
                result->GetByFieldIdx(26)->Get<float>(),
                result->GetByFieldIdx(27)->Get<float>(),
                result->GetByFieldIdx(28)->Get<float>(),
                result->GetByFieldIdx(29)->Get<float>(),
                0);

            Position const* transportPosition = m_movementInfo.GetTransportPos();

            if (!map::grid::IsValidMapCoord(
                GetPositionX() + transportPosition->x, GetPositionY() + transportPosition->y,
                GetPositionZ() + transportPosition->z, GetOrientation() + transportPosition->o) ||
                // transport size limited
                transportPosition->x > 50 || transportPosition->y > 50 || transportPosition->z > 50)
            {
                cftf::loger::SysLog::Instance().Error(std::format("{} have invalid transport coordinates (X: {} Y: {} Z: {} O: {}). Teleport to default race/class locations.",
                    guid.GetString().c_str(), GetPositionX() + transportPosition->x, GetPositionY() + transportPosition->y,
                    GetPositionZ() + transportPosition->z, GetOrientation() + transportPosition->o));

                RelocateToHomebind();

                m_movementInfo.ClearTransportData();

                transGUID = 0;
            }
        }

        if (transGUID != 0)
        {
            for (auto iter = MapMgr::Instance().m_Transports.begin(); iter != MapMgr::Instance().m_Transports.end(); ++iter)
            {
                if ((*iter)->GetGUIDLow() == transGUID)
                {
                    m_transport = *iter;
                    m_transport->AddPassenger(this);
                    SetLocationMapId(m_transport->GetMapId());
                    break;
                }
            }

            if (!m_transport)
            {
                cftf::loger::SysLog::Instance().Error(std::format("{} have problems with transport guid ({}). Teleport to default race/class locations.",
                    guid.GetString().c_str(), transGUID));

                RelocateToHomebind();

                m_movementInfo.ClearTransportData();

                transGUID = 0;
            }
        }

        // player bounded instance saves loaded in _LoadBoundInstances, group versions at group loading
        DungeonPersistentState* state = GetBoundInstanceSaveForSelfOrGroup(GetMapId());
        // load the player's map here if it's not already loaded
        SetMap(MapMgr::Instance().CreateMap(GetMapId(), this).get());
        // if the player is in an instance and it has been reset in the meantime teleport him to the entrance
        if (GetInstanceId() && !state)
        {
#if 0
            AreaTrigger const* at = sObjectMgr.GetMapEntranceTrigger(GetMapId());
            if (at)
            {
                Relocate(at->target_X, at->target_Y, at->target_Z, at->target_Orientation);
            }
            else
            {
                sLog.outError("Player %s(GUID: %u) logged in to a reset instance (map: %u) and there is no area-trigger leading to this map. Thus he can't be ported back to the entrance. This _might_ be an exploit attempt.", GetName(), GetGUIDLow(), GetMapId());
            }
#endif
        }

        SaveRecallPosition();

        time_t now = time(nullptr);
        time_t logoutTime = time_t(result->GetByFieldIdx(22)->Get<long long>());

        // since last logout (in seconds)
        unsigned int time_diff = unsigned int(now - logoutTime);

        // set value, including drunk invisibility detection
        // calculate sobering. after 15 minutes logged out, the player will be sober again
        float soberFactor;
        if (time_diff > 15 * MINUTE)
        {
            soberFactor = 0;
        }
        else
        {
            soberFactor = 1 - time_diff / (15.0f * (float)MINUTE);
        }
        unsigned short newDrunkenValue = unsigned short(soberFactor * m_drunk);
        SetDrunkValue(newDrunkenValue);

        m_cinematic = result->GetByFieldIdx(18)->Get<unsigned int>();
        m_Played_time[PLAYED_TIME_TOTAL] = result->GetByFieldIdx(19)->Get<unsigned int>();
        m_Played_time[PLAYED_TIME_LEVEL] = result->GetByFieldIdx(20)->Get<unsigned int>();

        m_resetTalentsCost = result->GetByFieldIdx(24)->Get<unsigned int>();
        m_resetTalentsTime = time_t(result->GetByFieldIdx(25)->Get<long long>());

        // reserve some flags
        unsigned int old_safe_flags = GetUint32Value(PLAYER_FLAGS) & (PLAYER_FLAGS_HIDE_CLOAK | PLAYER_FLAGS_HIDE_HELM);
        if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GM))
        {
            SetUint32Value(PLAYER_FLAGS, 0 | old_safe_flags);
        }

        m_taxi.LoadTaxiMask(result->GetByFieldIdx(17)->Get<std::string>().c_str());            // must be before InitTaxiNodesForLevel

        unsigned int extraflags = result->GetByFieldIdx(31)->Get<unsigned int>();
        m_stableSlots = result->GetByFieldIdx(32)->Get<unsigned int>();
        if (m_stableSlots > MAX_PET_STABLES)
        {
            cftf::loger::SysLog::Instance().Error(std::format("Player can have not more {} stable slots, but have in DB {}", MAX_PET_STABLES, unsigned int(m_stableSlots)));
            m_stableSlots = MAX_PET_STABLES;
        }
        m_atLoginFlags = result->GetByFieldIdx(33)->Get<unsigned int>();

        m_deathExpireTime = (time_t)result->GetByFieldIdx(36)->Get<long long>();
        if (m_deathExpireTime > now + MAX_DEATH_COUNT * DEATH_EXPIRE_STEP)
        {
            m_deathExpireTime = now + MAX_DEATH_COUNT * DEATH_EXPIRE_STEP - 1;
        }
        std::string taxi_nodes = result->GetByFieldIdx(37)->Get<std::string>();

        // clear channel spell data (if saved at channel spell casting)
        SetChannelObjectGuid(ObjectGuid());
        SetUint32Value(UNIT_CHANNEL_SPELL, 0);
        // clear charm/summon related fields
        SetCharm(nullptr);
        SetPet(nullptr);
        SetTargetGuid(ObjectGuid());
        SetCharmerGuid(ObjectGuid());
        SetOwnerGuid(ObjectGuid());
        SetCreatorGuid(ObjectGuid());
        // reset some aura modifiers before aura apply

        SetGuidValue(PLAYER_FARSIGHT, ObjectGuid());
        SetUint32Value(PLAYER_TRACK_CREATURES, 0);
        SetUint32Value(PLAYER_TRACK_RESOURCES, 0);

        // cleanup aura list explicitly before skill load where some spells can be applied
        RemoveAllAuras();

        // make sure the unit is considered out of combat for proper loading
        ClearInCombat();

        // make sure the unit is considered not in duel for proper loading
        SetGuidValue(PLAYER_DUEL_ARBITER, ObjectGuid());
        SetUint32Value(PLAYER_DUEL_TEAM, 0);

        // reset stats before loading any modifiers
        InitStatsForLevel();
        // is it need, only in pre-2.x used and field byte removed later?
        if (GetPowerType() == POWER_RAGE || GetPowerType() == POWER_MANA)
        {
            SetByteValue(UNIT_FIELD_BYTES_1, 1, 0xEE);
        }

        // rest bonus can only be calculated after InitStatsForLevel()
        m_rest_bonus = result->GetByFieldIdx(21)->Get<float>();

        if (time_diff > 0)
        {
            SetRestBonus(GetRestBonus() + ComputeRest(time_diff, true, (result->GetByFieldIdx(23)->Get<unsigned int>() > 0)));
        }
        // load skills after InitStatsForLevel because it triggering aura apply also
        _LoadSkills(datasets.at(PLAYER_LOGIN_QUERY_LOADSKILLS));
        // apply original stats mods before spell loading or item equipment that call before equip _RemoveStatsMods()

        // Mail
        _LoadMails(datasets.at(PLAYER_LOGIN_QUERY_LOADMAILS));
        _LoadMailedItems(datasets.at(PLAYER_LOGIN_QUERY_LOADMAILEDITEMS));
        UpdateNextMailTimeAndUnreads();
        _LoadAuras(datasets.at(PLAYER_LOGIN_QUERY_LOADAURAS), time_diff);
        // add ghost flag (must be after aura load: PLAYER_FLAGS_GHOST set in aura)
        if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
        {
            m_deathState = DEAD;
        }
        _LoadSpells(datasets.at(PLAYER_LOGIN_QUERY_LOADSPELLS));
        // after spell load
        InitTalentForLevel();
        learnDefaultSpells();

        // after spell load, learn rewarded spell if need also
        _LoadQuestStatus(datasets.at(PLAYER_LOGIN_QUERY_LOADQUESTSTATUS));

        // must be before inventory (some items required reputation check)
#if 0
        m_reputationMgr.LoadFromDB(holder->GetResult(PLAYER_LOGIN_QUERY_LOADREPUTATION));
#endif

        _LoadInventory(datasets.at(PLAYER_LOGIN_QUERY_LOADINVENTORY), time_diff);
        _LoadItemLoot(datasets.at(PLAYER_LOGIN_QUERY_LOADITEMLOOT));

        // update items with duration and realtime
        UpdateItemDuration(time_diff, true);

        _LoadActions(datasets.at(PLAYER_LOGIN_QUERY_LOADACTIONS));
#if 0
        m_social = sSocialMgr.LoadFromDB(datasets.at(PLAYER_LOGIN_QUERY_LOADSOCIALLIST), GetObjectGuid());

        if (!m_taxi.LoadTaxiDestinationsFromString(taxi_nodes, GetTeam()))
        {
            // problems with taxi path loading
            TaxiNodesEntry const* nodeEntry = NULL;
            if (unsigned int node_id = m_taxi.GetTaxiSource())
            {
                nodeEntry = sTaxiNodesStore.LookupEntry(node_id);
            }

            if (!nodeEntry)                                     // don't know taxi start node, to homebind
            {
                sLog.outError("Character %u have wrong data in taxi destination list, teleport to homebind.", GetGUIDLow());
                RelocateToHomebind();
            }
            else                                                // have start node, to it
            {
                sLog.outError("Character %u have too short taxi destination list, teleport to original node.", GetGUIDLow());
                SetLocationMapId(nodeEntry->map_id);
                Relocate(nodeEntry->x, nodeEntry->y, nodeEntry->z, 0.0f);
            }

            // we can be relocated from taxi and still have an outdated Map pointer!
            // so we need to get a new Map pointer!
            SetMap(sMapMgr.CreateMap(GetMapId(), this));
            SaveRecallPosition();                           // save as recall also to prevent recall and fall from sky

            m_taxi.ClearTaxiDestinations();
        }
#endif

        if (unsigned int node_id = m_taxi.GetTaxiSource())
        {
            // save source node as recall coord to prevent recall and fall from sky
            auto const* nodeEntry = dbc::TaxiNodesEntry::GetElement(node_id);
            CFT_ASSERT(nodeEntry);                           // checked in m_taxi.LoadTaxiDestinationsFromString
            m_recallMap = nodeEntry->map_id;
            m_recallX = nodeEntry->x;
            m_recallY = nodeEntry->y;
            m_recallZ = nodeEntry->z;

            // flight will started later
        }
        // has to be called after last Relocate() in Player::LoadFromDB
        SetFallInformation(0, GetPositionZ());

        _LoadSpellCooldowns(datasets.at(PLAYER_LOGIN_QUERY_LOADSPELLCOOLDOWNS));

        // Spell code allow apply any auras to dead character in load time in aura/spell/item loading
        // Do now before stats re-calculation cleanup for ghost state unexpected auras
        if (!IsAlive())
        {
            RemoveAllAurasOnDeath();
        }
        // apply all stat bonuses from items and auras
        SetCanModifyStats(true);
        UpdateAllStats();
        // restore remembered power/health values (but not more max values)
        unsigned int savedhealth = result->GetByFieldIdx(45)->Get<unsigned int>();
        SetHealth(savedhealth > GetMaxHealth() ? GetMaxHealth() : savedhealth);
        for (unsigned int i = 0; i < MAX_POWERS; ++i)
        {
            unsigned int savedpower = result->GetByFieldIdx(46 + i)->Get<unsigned int>();
            SetPower(Powers(i), savedpower > GetMaxPower(Powers(i)) ? GetMaxPower(Powers(i)) : savedpower);
        }

        unsigned int createdDate = result->GetByFieldIdx(55)->Get<unsigned int>();
        SetCreatedDate(createdDate);

        cftf::loger::SysLog::Instance().Error(std::format("The value of player {} after load item and aura is: ", m_name.c_str()));
        outDebugStatsValues();

#if 0
        // all fields read
        delete result;
#endif
#if 0
        // GM state
        if (GetSession()->GetSecurity() > SEC_PLAYER)
        {
            switch (sWorld.getConfig(CONFIG_UINT32_GM_LOGIN_STATE))
            {
            default:
            case 0:                      break;             // disable
            case 1: SetGameMaster(true); break;             // enable
            case 2:                                         // save state
                if (extraflags & PLAYER_EXTRA_GM_ON)
                {
                    SetGameMaster(true);
                }
                break;
            }

            switch (sWorld.getConfig(CONFIG_UINT32_GM_VISIBLE_STATE))
            {
            default:
            case 0: SetGMVisible(false); break;             // invisible
            case 1:                      break;             // visible
            case 2:                                         // save state
                if (extraflags & PLAYER_EXTRA_GM_INVISIBLE)
                {
                    SetGMVisible(false);
                }
                break;
            }

            switch (sWorld.getConfig(CONFIG_UINT32_GM_ACCEPT_TICKETS))
            {
            default:
            case 0:                        break;           // disable
            case 1: SetAcceptTicket(true); break;           // enable
            case 2:                                         // save state
                if (extraflags & PLAYER_EXTRA_GM_ACCEPT_TICKETS)
                {
                    SetAcceptTicket(true);
                }
                break;
            }

            switch (sWorld.getConfig(CONFIG_UINT32_GM_CHAT))
            {
            default:
            case 0:                  break;                 // disable
            case 1: SetGMChat(true); break;                 // enable
            case 2:                                         // save state
                if (extraflags & PLAYER_EXTRA_GM_CHAT)
                {
                    SetGMChat(true);
                }
                break;
            }

            switch (sWorld.getConfig(CONFIG_UINT32_GM_WISPERING_TO))
            {
            default:
            case 0:                          break;         // disable
            case 1: SetAcceptWhispers(true); break;         // enable
            case 2:                                         // save state
                if (extraflags & PLAYER_EXTRA_ACCEPT_WHISPERS)
                {
                    SetAcceptWhispers(true);
                }
                break;
            }
        }
#endif
		return true;
	}

    void Player::SetVisibleItemSlot(unsigned int slot, Item* pItem)
    {
#if 0
        if (pItem)
        {
            SetGuidValue(PLAYER_VISIBLE_ITEM_1_CREATOR + (slot * MAX_VISIBLE_ITEM_OFFSET), pItem->GetGuidValue(ITEM_FIELD_CREATOR));

            int VisibleBase = PLAYER_VISIBLE_ITEM_1_0 + (slot * MAX_VISIBLE_ITEM_OFFSET);
            SetUInt32Value(VisibleBase + 0, pItem->GetEntry());

            for (int i = 0; i < MAX_INSPECTED_ENCHANTMENT_SLOT; ++i)
            {
                SetUInt32Value(VisibleBase + 1 + i, pItem->GetEnchantmentId(EnchantmentSlot(i)));
            }

            // Use SetInt16Value to prevent set high part to FFFF for negative value
            SetInt16Value(PLAYER_VISIBLE_ITEM_1_PROPERTIES + (slot * MAX_VISIBLE_ITEM_OFFSET), 0, pItem->GetItemRandomPropertyId());
            SetUInt32Value(PLAYER_VISIBLE_ITEM_1_PROPERTIES + 1 + (slot * MAX_VISIBLE_ITEM_OFFSET), pItem->GetItemSuffixFactor());
        }
        else
        {
            SetGuidValue(PLAYER_VISIBLE_ITEM_1_CREATOR + (slot * MAX_VISIBLE_ITEM_OFFSET), ObjectGuid());

            int VisibleBase = PLAYER_VISIBLE_ITEM_1_0 + (slot * MAX_VISIBLE_ITEM_OFFSET);
            SetUInt32Value(VisibleBase + 0, 0);

            for (int i = 0; i < MAX_INSPECTED_ENCHANTMENT_SLOT; ++i)
            {
                SetUInt32Value(VisibleBase + 1 + i, 0);
            }

            SetUInt32Value(PLAYER_VISIBLE_ITEM_1_PROPERTIES + 0 + (slot * MAX_VISIBLE_ITEM_OFFSET), 0);
            SetUInt32Value(PLAYER_VISIBLE_ITEM_1_PROPERTIES + 1 + (slot * MAX_VISIBLE_ITEM_OFFSET), 0);
        }
#endif
    }

    void Player::setFactionForRace(unsigned char race)
    {
#if 0
        m_team = TeamForRace(race);
#endif
#if 0
        setFaction(getFactionForRace(race));
#endif
    }

    unsigned int Player::getFactionForRace(unsigned char race)
    {
#if 1
        return 0;
#else
#endif
    }

    void Player::InitPrimaryProfessions()
    {
        SetFreePrimaryProfessions(World::Instance().getConfig(CONFIG_UINT32_MAX_PRIMARY_TRADE_SKILL));
    }

    void Player::_LoadHonorCP(std::shared_ptr<cftf::mysql::DataSet> ds)
    {
#if 0
        if (ds)
        {
            m_honorCP.clear();

            do
            {
                Field* fields = result->Fetch();

                HonorCP CP;
                CP.victimType = fields[0].GetUInt8();
                CP.victimID = fields[1].GetUInt32();
                CP.honorPoints = fields[2].GetFloat();
                CP.date = fields[3].GetUInt32();
                CP.type = fields[4].GetUInt8();
                CP.state = HK_UNCHANGED;
                CP.isKill = isKill(CP.victimType);

                m_honorCP.push_back(CP);
            } while (result->NextRow());

            delete result;
        }
#endif
    }

    void Player::_LoadBoundInstances(std::shared_ptr<cftf::mysql::DataSet> ds)
    {
#if 0
        m_boundInstances.clear();

        Group* group = GetGroup();

        // QueryResult *result = CharacterDatabase.PQuery("SELECT `id`, `permanent`, `map`, `resettime` FROM `character_instance` LEFT JOIN `instance` ON `instance` = `id` WHERE `guid` = '%u'", GUID_LOPART(m_guid));
        if (result)
        {
            do
            {
                Field* fields = result->Fetch();
                bool perm = fields[1].GetBool();
                unsigned int mapId = fields[2].GetUInt32();
                unsigned int instanceId = fields[0].GetUInt32();
                time_t resetTime = (time_t)fields[3].GetUInt64();
                // the resettime for normal instances is only saved when the InstanceSave is unloaded
                // so the value read from the DB may be wrong here but only if the InstanceSave is loaded
                // and in that case it is not used

                MapEntry const* mapEntry = sMapStore.LookupEntry(mapId);
                if (!mapEntry || !mapEntry->IsDungeon())
                {
                    sLog.outError("_LoadBoundInstances: player %s(%d) has bind to nonexistent or not dungeon map %d", GetName(), GetGUIDLow(), mapId);
                    CharacterDatabase.PExecute("DELETE FROM `character_instance` WHERE `guid` = '%u' AND `instance` = '%u'", GetGUIDLow(), instanceId);
                    continue;
                }

                if (!perm && group)
                {
                    sLog.outError("_LoadBoundInstances: %s is in group (Id: %d) but has a non-permanent character bind to map %d,%d",
                        GetGuidStr().c_str(), group->GetId(), mapId, instanceId);
                    CharacterDatabase.PExecute("DELETE FROM `character_instance` WHERE `guid` = '%u' AND `instance` = '%u'",
                        GetGUIDLow(), instanceId);
                    continue;
                }

                // since non permanent binds are always solo bind, they can always be reset
                DungeonPersistentState* state = (DungeonPersistentState*)sMapPersistentStateMgr.AddPersistentState(mapEntry, instanceId, resetTime, !perm, true);
                if (state)
                {
                    BindToInstance(state, perm, true);
                }
            } while (result->NextRow());
            delete result;
        }
#endif
    }

    bool Player::IsPositionValid() const
    {
        return map::grid::IsValidMapCoord(m_position.x, m_position.y, m_position.z, m_position.o);
    }

    void Player::_LoadIntoDataField(const char* data, unsigned int startOffset, unsigned int count)
    {
#if 0
        if (!data)
        {
            return;
        }

        Tokens tokens = StrSplit(data, " ");

        if (tokens.size() != count)
        {
            return;
        }

        Tokens::iterator iter;
        unsigned int index;
        for (iter = tokens.begin(), index = 0; index < count; ++iter, ++index)
        {
            m_uint32Values[startOffset + index] = atol((*iter).c_str());
        }
#endif
    }

    void Player::InitDisplayIds()
    {
        auto const* info = mt::PlayerCreateInfo::GetByRaceClass(getRace(), getClass());
        if (!info)
        {
            cftf::loger::SysLog::Instance().Error(std::format("Player {} has incorrect race/class pair. Can't init display ids.", GetGUIDLow()));
            return;
        }

        unsigned char gender = getGender();
        switch (gender)
        {
        case GENDER_FEMALE:
            // workaround for tauren scale
            if (getRace() == RACE_TAUREN)
            {
                SetObjectScale(DEFAULT_TAUREN_FEMALE_SCALE);
            }
            else
            {
                SetObjectScale(DEFAULT_OBJECT_SCALE);
            }

            SetDisplayId(info->displayId_f);
            SetNativeDisplayId(info->displayId_f);
            break;
        case GENDER_MALE:
            // workaround for tauren scale
            if (getRace() == RACE_TAUREN)
            {
                SetObjectScale(DEFAULT_TAUREN_MALE_SCALE);
            }
            else
            {
                SetObjectScale(DEFAULT_OBJECT_SCALE);
            }

            SetDisplayId(info->displayId_m);
            SetNativeDisplayId(info->displayId_m);
            break;
        default:
            cftf::loger::SysLog::Instance().Error(std::format("Invalid gender {} for player", gender));
            return;
        }
    }

    void Player::MoneyChanged(unsigned int count)
    {
#if 0
        for (int i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
        {
            unsigned int questid = GetQuestSlotQuestId(i);
            if (!questid)
            {
                continue;
            }

            Quest const* qInfo = sObjectMgr.GetQuestTemplate(questid);
            if (qInfo && qInfo->GetRewOrReqMoney() < 0)
            {
                QuestStatusData& q_status = mQuestStatus[questid];

                if (q_status.m_status == QUEST_STATUS_INCOMPLETE)
                {
                    if (int32(count) >= -qInfo->GetRewOrReqMoney())
                    {
                        if (CanCompleteQuest(questid))
                        {
                            CompleteQuest(questid);
                        }
                    }
                }
                else if (q_status.m_status == QUEST_STATUS_COMPLETE)
                {
                    if (int32(count) < -qInfo->GetRewOrReqMoney())
                    {
                        IncompleteQuest(questid);
                    }
                }
            }
        }
#endif
    }

    void Player::outDebugStatsValues() const
    {
#if 0
        // optimize disabled debug output
        if (!sLog.HasLogLevelOrHigher(LOG_LVL_DEBUG) || sLog.HasLogFilter(LOG_FILTER_PLAYER_STATS))
        {
            return;
        }
#endif

#if 0
        sLog.outDebug("HP is: \t\t\t%u\t\tMP is: \t\t\t%u", GetMaxHealth(), GetMaxPower(POWER_MANA));
        sLog.outDebug("AGILITY is: \t\t%f\t\tSTRENGTH is: \t\t%f", GetStat(STAT_AGILITY), GetStat(STAT_STRENGTH));
        sLog.outDebug("INTELLECT is: \t\t%f\t\tSPIRIT is: \t\t%f", GetStat(STAT_INTELLECT), GetStat(STAT_SPIRIT));
        sLog.outDebug("STAMINA is: \t\t%f", GetStat(STAT_STAMINA));
        sLog.outDebug("Armor is: \t\t%u\t\tBlock is: \t\t%f", GetArmor(), GetFloatValue(PLAYER_BLOCK_PERCENTAGE));
        sLog.outDebug("HolyRes is: \t\t%u\t\tFireRes is: \t\t%u", GetResistance(SPELL_SCHOOL_HOLY), GetResistance(SPELL_SCHOOL_FIRE));
        sLog.outDebug("NatureRes is: \t\t%u\t\tFrostRes is: \t\t%u", GetResistance(SPELL_SCHOOL_NATURE), GetResistance(SPELL_SCHOOL_FROST));
        sLog.outDebug("ShadowRes is: \t\t%u\t\tArcaneRes is: \t\t%u", GetResistance(SPELL_SCHOOL_SHADOW), GetResistance(SPELL_SCHOOL_ARCANE));
        sLog.outDebug("MIN_DAMAGE is: \t\t%f\tMAX_DAMAGE is: \t\t%f", GetFloatValue(UNIT_FIELD_MINDAMAGE), GetFloatValue(UNIT_FIELD_MAXDAMAGE));
        sLog.outDebug("MIN_OFFHAND_DAMAGE is: \t%f\tMAX_OFFHAND_DAMAGE is: \t%f", GetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE), GetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE));
        sLog.outDebug("MIN_RANGED_DAMAGE is: \t%f\tMAX_RANGED_DAMAGE is: \t%f", GetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE), GetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE));
        sLog.outDebug("ATTACK_TIME is: \t%u\t\tRANGE_ATTACK_TIME is: \t%u", GetAttackTime(BASE_ATTACK), GetAttackTime(RANGED_ATTACK));
#endif
    }

    bool Player::_LoadHomeBind(std::shared_ptr<cftf::mysql::DataSet> ds)
    {
#if 1
        return true;
#else
#endif
    }

    void Player::_LoadBGData(std::shared_ptr<cftf::mysql::DataSet> ds)
    {
#if 0
        if (!result)
        {
            return;
        }

        // Expecting only one row
        Field* fields = result->Fetch();
        /* bgInstanceID, bgTeam, x, y, z, o, map */
        m_bgData.bgInstanceID = fields[0].GetUInt32();
        m_bgData.bgTeam = Team(fields[1].GetUInt32());
        m_bgData.joinPos = WorldLocation(fields[6].GetUInt32(),    // Map
            fields[2].GetFloat(),     // X
            fields[3].GetFloat(),     // Y
            fields[4].GetFloat(),     // Z
            fields[5].GetFloat());    // Orientation

        delete result;
#endif
    }

    DungeonPersistentState* Player::GetBoundInstanceSaveForSelfOrGroup(unsigned int mapid)
    {
#if 1
        return nullptr;
#else
        MapEntry const* mapEntry = sMapStore.LookupEntry(mapid);
        if (!mapEntry)
        {
            return NULL;
        }

        InstancePlayerBind* pBind = GetBoundInstance(mapid);
        DungeonPersistentState* state = pBind ? pBind->state : NULL;

        // the player's permanent player bind is taken into consideration first
        // then the player's group bind and finally the solo bind.
        if (!pBind || !pBind->perm)
        {
            InstanceGroupBind* groupBind = NULL;
            if (Group* group = GetGroup())
                if ((groupBind = group->GetBoundInstance(mapid)))
                {
                    state = groupBind->state;
                }
        }

        return state;
#endif
    }

    void Player::SaveRecallPosition()
    {
        m_recallMap = GetMapId();
        m_recallX = GetPositionX();
        m_recallY = GetPositionY();
        m_recallZ = GetPositionZ();
        m_recallO = GetOrientation();
    }

    void Player::SetDrunkValue(unsigned short newDrunkenValue, unsigned int /*itemId*/)
    {
        unsigned int oldDrunkenState = Player::GetDrunkenstateByValue(m_drunk);

        m_drunk = newDrunkenValue;
        SetUInt16Value(PLAYER_BYTES_3, 0, unsigned short(getGender()) | (m_drunk & 0xFFFE));

        unsigned int newDrunkenState = Player::GetDrunkenstateByValue(m_drunk);

#if 0
        // special drunk invisibility detection
        if (newDrunkenState >= DRUNKEN_DRUNK)
        {
            m_detectInvisibilityMask |= (1 << 6);
        }
        else
        {
            m_detectInvisibilityMask &= ~(1 << 6);
        }
#endif
    }

    DrunkenState Player::GetDrunkenstateByValue(unsigned short value)
    {
        if (value >= 23000)
        {
            return DRUNKEN_SMASHED;
        }
        if (value >= 12800)
        {
            return DRUNKEN_DRUNK;
        }
        if (value & 0xFFFE)
        {
            return DRUNKEN_TIPSY;
        }
        return DRUNKEN_SOBER;
    }

    //== PlayerTaxi ================================================

    PlayerTaxi::PlayerTaxi()
    {
        // Taxi nodes
        memset(m_taximask, 0, sizeof(m_taximask));
    }

    void PlayerTaxi::InitTaxiNodes(unsigned int race, unsigned int /*level*/)
    {
        memset(m_taximask, 0, sizeof(m_taximask));
#if 0
        // capital and taxi hub masks
        ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race);
        m_taximask[0] = rEntry->startingTaxiMask;
#endif
    }

    void PlayerTaxi::LoadTaxiMask(const char* data)
    {
#if 0
        Tokens tokens = StrSplit(data, " ");

        int index;
        Tokens::iterator iter;
        for (iter = tokens.begin(), index = 0; (index < TaxiMaskSize) && (iter != tokens.end()); ++iter, ++index)
        {
            // load and set bits only for existing taxi nodes
            m_taximask[index] = sTaxiNodesMask[index] & uint32(atol((*iter).c_str()));
        }
#endif
    }

#if 0
    void PlayerTaxi::AppendTaximaskTo(ByteBuffer& data, bool all)
    {
        for (unsigned char i = 0; i < TaxiMaskSize; ++i)
        {
            data << uint32(all ? sTaxiNodesMask[i] : m_taximask[i]); // all or just known existing nodes
        }
    }
#endif

    bool PlayerTaxi::LoadTaxiDestinationsFromString(const std::string& values, Team team)
    {
        ClearTaxiDestinations();
#if 0
        Tokens tokens = StrSplit(values, " ");

        for (Tokens::iterator iter = tokens.begin(); iter != tokens.end(); ++iter)
        {
            unsigned int node = uint32(atol(iter->c_str()));
            AddTaxiDestination(node);
        }

        if (m_TaxiDestinations.empty())
        {
            return true;
        }

        // Check integrity
        if (m_TaxiDestinations.size() < 2)
        {
            return false;
        }

        for (size_t i = 1; i < m_TaxiDestinations.size(); ++i)
        {
            unsigned int cost;
            unsigned int path;
            sObjectMgr.GetTaxiPath(m_TaxiDestinations[i - 1], m_TaxiDestinations[i], path, cost);
            if (!path)
            {
                return false;
            }
        }

        // can't load taxi path without mount set (quest taxi path?)
        if (!sObjectMgr.GetTaxiMountDisplayId(GetTaxiSource(), team, true))
        {
            return false;
        }
#endif
        return true;
    }

    std::string PlayerTaxi::SaveTaxiDestinationsToString()
    {
#if 1
        return "";
#else
        if (m_TaxiDestinations.empty())
        {
            return "";
        }

        std::ostringstream ss;

        for (size_t i = 0; i < m_TaxiDestinations.size(); ++i)
        {
            ss << m_TaxiDestinations[i] << " ";
        }

        return ss.str();
#endif
    }

    unsigned int PlayerTaxi::GetCurrentTaxiPath() const
    {
        if (m_TaxiDestinations.size() < 2)
        {
            return 0;
        }

        unsigned int path = 0;
        unsigned int cost = 0;

#if 0
        sObjectMgr.GetTaxiPath(m_TaxiDestinations[0], m_TaxiDestinations[1], path, cost);
#endif
        return path;
    }

    void Player::_RemoveAllItemMods()
    {
        cftf::loger::SysLog::Instance().Deubg("_RemoveAllItemMods start.");

#if 0
        for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
        {
            if (m_items[i])
            {
                ItemPrototype const* proto = m_items[i]->GetProto();
                if (!proto)
                {
                    continue;
                }

                // item set bonuses not dependent from item broken state
                if (proto->ItemSet)
                {
                    RemoveItemsSetItem(this, proto);
                }

                if (m_items[i]->IsBroken())
                {
                    continue;
                }

                ApplyItemEquipSpell(m_items[i], false);
                ApplyEnchantment(m_items[i], false);
            }
        }

        for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
        {
            if (m_items[i])
            {
                if (m_items[i]->IsBroken())
                {
                    continue;
                }
                ItemPrototype const* proto = m_items[i]->GetProto();
                if (!proto)
                {
                    continue;
                }

                unsigned intattacktype = Player::GetAttackBySlot(i);
                if (attacktype < MAX_ATTACK)
                {
                    _ApplyWeaponDependentAuraMods(m_items[i], WeaponAttackType(attacktype), false);
                }

                _ApplyItemBonuses(proto, i, false);

                if (i == EQUIPMENT_SLOT_RANGED)
                {
                    _ApplyAmmoBonuses();
                }
            }
        }
#endif
        cftf::loger::SysLog::Instance().Info("_RemoveAllItemMods complete.");
    }

    void Player::_RemoveAllStatBonuses()
    {
        SetCanModifyStats(false);

        _RemoveAllItemMods();
#if 0
        _RemoveAllAuraMods();
#endif
        SetCanModifyStats(true);

        UpdateAllStats();
    }

    void Player::UpdateSkillsForLevel()
    {
        unsigned short maxconfskill = World::Instance().GetConfigMaxSkillValue();
        unsigned int maxSkill = GetMaxSkillValueForLevel();

        bool alwaysMaxSkill = World::Instance().getConfig(CONFIG_BOOL_ALWAYS_MAX_SKILL_FOR_LEVEL);

        for (auto itr = mSkillStatus.begin(); itr != mSkillStatus.end(); ++itr)
        {
            SkillStatusData& skillStatus = itr->second;
            if (skillStatus.uState == SKILL_DELETED)
            {
                continue;
            }

            unsigned int pskill = itr->first;

            dbc::SkillLineEntry const* pSkill = dbc::SkillLineEntry::GetElement(pskill);
            if (!pSkill)
            {
                continue;
            }

            if (pSkill->GetSkillRangeType(false) != SKILL_RANGE_LEVEL)
            {
                continue;
            }

            unsigned int valueIndex = PLAYER_SKILL_VALUE_INDEX(skillStatus.pos);
            unsigned int data = GetUint32Value(valueIndex);
            unsigned int max = SKILL_MAX(data);
            unsigned int val = SKILL_VALUE(data);

            /// update only level dependent max skill values
            if (max != 1)
            {
                /// maximize skill always
                if (alwaysMaxSkill)
                {
                    SetUint32Value(valueIndex, MAKE_SKILL_VALUE(maxSkill, maxSkill));
                    if (skillStatus.uState != SKILL_NEW)
                    {
                        skillStatus.uState = SKILL_CHANGED;
                    }
                }
                else if (max != maxconfskill)                   /// update max skill value if current max skill not maximized
                {
                    SetUint32Value(valueIndex, MAKE_SKILL_VALUE(val, maxSkill));
                    if (skillStatus.uState != SKILL_NEW)
                    {
                        skillStatus.uState = SKILL_CHANGED;
                    }
                }
            }
        }
    }

    Item* Player::GetWeaponForAttack(WeaponAttackType attackType, bool nonbroken, bool useable) const
    {
        unsigned char slot = 0;
        switch (attackType)
        {
        case BASE_ATTACK:   slot = EQUIPMENT_SLOT_MAINHAND; break;
        case OFF_ATTACK:    slot = EQUIPMENT_SLOT_OFFHAND;  break;
        case RANGED_ATTACK: slot = EQUIPMENT_SLOT_RANGED;   break;
        default: return NULL;
        }

        Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
        if (!item || item->GetProto()->class_ != mt::Item::ITEM_CLASS_WEAPON)
        {
            return NULL;
        }

        if (useable && !CanUseEquippedWeapon(attackType))
        {
            return NULL;
        }

        if (nonbroken && item->IsBroken())
        {
            return NULL;
        }

        return item;
    }

    Item* Player::GetItemByPos(unsigned char bag, unsigned char slot) const
    {
        if (bag == INVENTORY_SLOT_BAG_0 &&
            (slot < BANK_SLOT_BAG_END || // Equiped, Default Bagpack Inventory, Default Bank SLots
                slot >= BUYBACK_SLOT_START
                )
            && slot < KEYRING_SLOT_END
            )
        {
            return m_items[slot];
        }
        else if ((bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END)
            || (bag >= BANK_SLOT_BAG_START && bag < BANK_SLOT_BAG_END))
        {
            Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, bag);
            if (pBag)
            {
                return pBag->GetItemByPos(slot);
            }
        }
        return NULL;
    }

    void Player::SetRegularAttackTime()
    {
        for (int i = 0; i < MAX_ATTACK; ++i)
        {
            Item* tmpitem = GetWeaponForAttack(WeaponAttackType(i), true, false);
            if (tmpitem)
            {
                mt::Item const* proto = tmpitem->GetProto();
                if (proto->delay)
                {
                    SetAttackTime(WeaponAttackType(i), proto->delay);
                }
                else
                {
                    SetAttackTime(WeaponAttackType(i), BASE_ATTACK_TIME);
                }
            }
        }
    }

    void Player::ApplyEquipSpell(dbc::SpellEntry const* spellInfo, Item* item, bool apply, bool form_change)
    {
        if (apply)
        {
            // Can not be used in this stance/form
            if (spellInfo->GetErrorAtShapeshiftedCast(GetShapeshiftForm()) != SPELL_CAST_OK)
            {
                return;
            }

            if (form_change)                                    // check aura active state from other form
            {
                bool found = false;
                for (int k = 0; k < MAX_EFFECT_INDEX; ++k)
                {
#if 0
                    SpellAuraHolderBounds spair = GetSpellAuraHolderBounds(spellInfo->Id);
                    for (SpellAuraHolderMap::const_iterator iter = spair.first; iter != spair.second; ++iter)
                    {
                        if (!item || iter->second->GetCastItemGuid() == item->GetObjectGuid())
                        {
                            found = true;
                            break;
                        }
                    }
                    if (found)
                    {
                        break;
                    }
#endif
                }

                if (found)                                      // and skip re-cast already active aura at form change
                {
                    return;
                }
            }

            cftf::loger::SysLog::Instance().Info(std::format("WORLD: cast {} Equip spellId - {}", (item ? "item" : "itemset"), spellInfo->Id));

#if 0
            CastSpell(this, spellInfo, true, item);
#endif
        }
        else
        {
            if (form_change)                                    // check aura compatibility
            {
#if 0
                // Can not be used in this stance/form
                if (GetErrorAtShapeshiftedCast(spellInfo, GetShapeshiftForm()) == SPELL_CAST_OK)
                {
                    return; // and remove only not compatible at form change
                }
#endif
            }

#if 0
            if (item)
            {
                RemoveAurasDueToItemSpell(item, spellInfo->Id); // un-apply all spells , not only at-equipped
            }
            else
            {
                RemoveAurasDueToSpell(spellInfo->Id); // un-apply spell (item set case)
            }
#endif
        }
    }

    void Player::ApplyItemEquipSpell(Item* item, bool apply, bool form_change)
    {
        if (!item)
        {
            return;
        }

        auto const* proto = item->GetProto();
        if (!proto)
        {
            return;
        }

        for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
        {
            auto spellData = proto->Spells[i];

            // no spell
            if (!spellData->SpellId)
            {
                continue;
            }

            if (apply)
            {
                // apply only at-equip spells
                if (spellData->SpellTrigger != mt::Item::ITEM_SPELLTRIGGER_ON_EQUIP)
                {
                    continue;
                }
            }
            else
            {
                // at un-apply remove all spells (not only at-apply, so any at-use active affects from item and etc)
                // except on form change and with at-use with negative charges, so allow consuming item spells (including with extra flag that prevent consume really)
                // applied to player after item remove from equip slot
                if (spellData->SpellTrigger == mt::Item::ITEM_SPELLTRIGGER_ON_USE && (form_change || spellData->SpellCharges < 0))
                {
                    continue;
                }
            }

            // check if it is valid spell
            auto const* spellproto = dbc::SpellEntry::GetElement(spellData->SpellId);
            if (!spellproto)
            {
                continue;
            }

            ApplyEquipSpell(spellproto, item, apply, form_change);
        }
    }

    void Player::UpdateEquipSpellsAtFormChange()
    {
        for (int i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
        {
            if (m_items[i] && !m_items[i]->IsBroken())
            {
                ApplyItemEquipSpell(m_items[i], false, true);   // remove spells that not fit to form
                ApplyItemEquipSpell(m_items[i], true, true);    // add spells that fit form but not active
            }
        }

        // item set bonuses not dependent from item broken state
        for (size_t setindex = 0; setindex < ItemSetEff.size(); ++setindex)
        {
            auto eff = ItemSetEff[setindex];
            if (!eff)
            {
                continue;
            }

            for (unsigned int y = 0; y < 8; ++y)
            {
                auto const* spellInfo = eff->spells[y];
                if (!spellInfo)
                {
                    continue;
                }

                ApplyEquipSpell(spellInfo, NULL, false, true);  // remove spells that not fit to form
                ApplyEquipSpell(spellInfo, NULL, true, true);   // add spells that fit form but not active
            }
        }
    }

    void Player::UpdateAttackPowerAndDamage(bool ranged)
    {
        float val2 = 0.0f;
        float level = float(getLevel());

        UnitMods unitMod = ranged ? UNIT_MOD_ATTACK_POWER_RANGED : UNIT_MOD_ATTACK_POWER;

        unsigned short index = UNIT_FIELD_ATTACK_POWER;
        unsigned short index_mod = UNIT_FIELD_ATTACK_POWER_MODS;
        unsigned short index_mult = UNIT_FIELD_ATTACK_POWER_MULTIPLIER;

        if (ranged)
        {
            index = UNIT_FIELD_RANGED_ATTACK_POWER;
            index_mod = UNIT_FIELD_RANGED_ATTACK_POWER_MODS;
            index_mult = UNIT_FIELD_RANGED_ATTACK_POWER_MULTIPLIER;

            switch (getClass())
            {
            case CLASS_HUNTER: val2 = level * 2.0f + GetStat(STAT_AGILITY) * 2.0f - 10.0f;    break;
            case CLASS_ROGUE:  val2 = level + GetStat(STAT_AGILITY) - 10.0f;    break;
            case CLASS_WARRIOR: val2 = level + GetStat(STAT_AGILITY) - 10.0f;    break;
            case CLASS_DRUID:
                switch (GetShapeshiftForm())
                {
                case FORM_CAT:
                case FORM_BEAR:
                case FORM_DIREBEAR:
                    val2 = 0.0f; break;
                default:
                    val2 = GetStat(STAT_AGILITY) - 10.0f; break;
                }
                break;
            default: val2 = GetStat(STAT_AGILITY) - 10.0f; break;
            }
        }
        else
        {
            switch (getClass())
            {
            case CLASS_WARRIOR:      val2 = level * 3.0f + GetStat(STAT_STRENGTH) * 2.0f - 20.0f; break;
            case CLASS_PALADIN:      val2 = level * 3.0f + GetStat(STAT_STRENGTH) * 2.0f - 20.0f; break;
            case CLASS_ROGUE:        val2 = level * 2.0f + GetStat(STAT_STRENGTH) + GetStat(STAT_AGILITY) - 20.0f; break;
            case CLASS_HUNTER:       val2 = level * 2.0f + GetStat(STAT_STRENGTH) + GetStat(STAT_AGILITY) - 20.0f; break;
            case CLASS_SHAMAN:       val2 = level * 2.0f + GetStat(STAT_STRENGTH) * 2.0f - 20.0f; break;
            case CLASS_DRUID:
            {
                ShapeshiftForm form = GetShapeshiftForm();
                // Check if Predatory Strikes is skilled
                float mLevelMult = 0.0;
                switch (form)
                {
                case FORM_CAT:
                case FORM_BEAR:
                case FORM_DIREBEAR:
                case FORM_MOONKIN:
                {
#if 0
                    Unit::AuraList const& mDummy = GetAurasByType(SPELL_AURA_DUMMY);
                    for (Unit::AuraList::const_iterator itr = mDummy.begin(); itr != mDummy.end(); ++itr)
                    {
                        // Predatory Strikes
                        if ((*itr)->GetSpellProto()->SpellIconID == 1563)
                        {
                            mLevelMult = (*itr)->GetModifier()->m_amount / 100.0f;
                            break;
                        }
                    }
#endif
                    break;
                }
                default: break;
                }

                switch (form)
                {
                case FORM_CAT:
                    val2 = getLevel() * mLevelMult + GetStat(STAT_STRENGTH) * 2.0f + GetStat(STAT_AGILITY) - 20.0f; break;
                case FORM_BEAR:
                case FORM_DIREBEAR:
                    val2 = getLevel() * mLevelMult + GetStat(STAT_STRENGTH) * 2.0f - 20.0f; break;
                case FORM_MOONKIN:
                    val2 = getLevel() * (mLevelMult + 1.5f) + GetStat(STAT_STRENGTH) * 2.0f - 20.0f; break;
                default:
                    val2 = GetStat(STAT_STRENGTH) * 2.0f - 20.0f; break;
                }
                break;
            }
            case CLASS_MAGE:    val2 = GetStat(STAT_STRENGTH) - 10.0f; break;
            case CLASS_PRIEST:  val2 = GetStat(STAT_STRENGTH) - 10.0f; break;
            case CLASS_WARLOCK: val2 = GetStat(STAT_STRENGTH) - 10.0f; break;
            }
        }

        SetModifierValue(unitMod, BASE_VALUE, val2);

        float base_attPower = GetModifierValue(unitMod, BASE_VALUE) * GetModifierValue(unitMod, BASE_PCT);
        float attPowerMod = GetModifierValue(unitMod, TOTAL_VALUE);

        float attPowerMultiplier = GetModifierValue(unitMod, TOTAL_PCT) - 1.0f;
#if 0
        SetInt32Value(index, (uint32)base_attPower);            // UNIT_FIELD_(RANGED)_ATTACK_POWER field
        SetInt32Value(index_mod, (uint32)attPowerMod);          // UNIT_FIELD_(RANGED)_ATTACK_POWER_MODS field
        SetFloatValue(index_mult, attPowerMultiplier);          // UNIT_FIELD_(RANGED)_ATTACK_POWER_MULTIPLIER field

        // automatically update weapon damage after attack power modification
        if (ranged)
        {
            UpdateDamagePhysical(RANGED_ATTACK);
        }
        else
        {
            UpdateDamagePhysical(BASE_ATTACK);
            if (CanDualWield() && haveOffhandWeapon())          // allow update offhand damage only if player knows DualWield Spec and has equipped offhand weapon
            {
                UpdateDamagePhysical(OFF_ATTACK);
            }
        }
#endif
    }

    void Player::InitDataForForm(bool reapplyMods)
    {
        ShapeshiftForm form = GetShapeshiftForm();

        switch (form)
        {
        case FORM_CAT:
        {
            SetAttackTime(BASE_ATTACK, 1000);               // Speed 1
            SetAttackTime(OFF_ATTACK, 1000);                // Speed 1

            if (GetPowerType() != POWER_ENERGY)
            {
                SetPowerType(POWER_ENERGY);
            }
            break;
        }
        case FORM_BEAR:
        case FORM_DIREBEAR:
        {
            SetAttackTime(BASE_ATTACK, 2500);               // Speed 2.5
            SetAttackTime(OFF_ATTACK, 2500);                // Speed 2.5

            if (GetPowerType() != POWER_RAGE)
            {
                SetPowerType(POWER_RAGE);
            }
            break;
        }
        default:                                            // 0, for example
        {
            SetRegularAttackTime();

            dbc::ChrClassesEntry const* cEntry = dbc::ChrClassesEntry::GetElement(getClass());
            if (cEntry && cEntry->powerType < MAX_POWERS && unsigned int(GetPowerType()) != cEntry->powerType)
            {
                SetPowerType(Powers(cEntry->powerType));
            }

            break;
        }
        }

        // update auras at form change, ignore this at mods reapply (.reset stats/etc) when form not change.
        if (!reapplyMods)
        {
            UpdateEquipSpellsAtFormChange();
        }

#if 0
        UpdateAttackPowerAndDamage();
        UpdateAttackPowerAndDamage(true);
#endif
    }

    void Player::InitStatsForLevel(bool reapplyMods)
    {
        if (reapplyMods)                                        // reapply stats values only on .reset stats (level) command
        {
            _RemoveAllStatBonuses();
        }

        mt::PlayerCreateInfo::PlayerClassLevelInfo classInfo;
        mt::PlayerCreateInfo::GetPlayerClassLevelInfo(getClass(), getLevel(), &classInfo);

        mt::PlayerCreateInfo::PlayerLevelInfo info;
        mt::PlayerCreateInfo::GetPlayerLevelInfo(getRace(), getClass(), getLevel(), &info);

        SetUint32Value(PLAYER_NEXT_LEVEL_XP, mt::PlayerXpForLevel::GetElement(getLevel())->xp_for_next_level);

        // reset before any aura state sources (health set/aura apply)
        SetUint32Value(UNIT_FIELD_AURASTATE, 0);

        UpdateSkillsForLevel();

        // set default cast time multiplier
        SetFloatValue(UNIT_MOD_CAST_SPEED, 1.0f);

        // save base values (bonuses already included in stored stats
        for (int i = STAT_STRENGTH; i < MAX_STATS; ++i)
        {
            SetCreateStat(Stats(i), info.stats[i]);
        }

        for (int i = STAT_STRENGTH; i < MAX_STATS; ++i)
        {
            SetStat(Stats(i), info.stats[i]);
        }

        SetCreateHealth(classInfo.basehealth);

        // set create powers
        SetCreateMana(classInfo.basemana);

        SetArmor(int(m_createStats[STAT_AGILITY] * 2));

        InitStatBuffMods();

        //[-ZERO] SetUInt32Value(PLAYER_FIELD_MOD_HEALING_DONE_POS,0);
        for (int i = 0; i < MAX_SPELL_SCHOOL; ++i)
        {
            SetUint32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG + i, 0);
            SetUint32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + i, 0);
            SetFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT + i, 1.00f);
        }

        // reset attack power, damage and attack speed fields
        SetFloatValue(UNIT_FIELD_BASEATTACKTIME, 2000.0f);
        SetFloatValue(UNIT_FIELD_BASEATTACKTIME + 1, 2000.0f);  // offhand attack time
        SetFloatValue(UNIT_FIELD_RANGEDATTACKTIME, 2000.0f);

        SetFloatValue(UNIT_FIELD_MINDAMAGE, 0.0f);
        SetFloatValue(UNIT_FIELD_MAXDAMAGE, 0.0f);
        SetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE, 0.0f);
        SetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE, 0.0f);
        SetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE, 0.0f);
        SetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE, 0.0f);

        SetInt32Value(UNIT_FIELD_ATTACK_POWER, 0);
        SetInt32Value(UNIT_FIELD_ATTACK_POWER_MODS, 0);
        SetFloatValue(UNIT_FIELD_ATTACK_POWER_MULTIPLIER, 0.0f);
        SetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER, 0);
        SetInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER_MODS, 0);
        SetFloatValue(UNIT_FIELD_RANGED_ATTACK_POWER_MULTIPLIER, 0.0f);

        // Base crit values (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset
        SetFloatValue(PLAYER_CRIT_PERCENTAGE, 0.0f);
        SetFloatValue(PLAYER_RANGED_CRIT_PERCENTAGE, 0.0f);

        // Init spell schools (will be recalculated in UpdateAllStats() at loading and in _ApplyAllStatBonuses() at reset
        for (unsigned char i = 0; i < MAX_SPELL_SCHOOL; ++i)
        {
            m_SpellCritPercentage[i] = 0.0f;
        }

        SetFloatValue(PLAYER_PARRY_PERCENTAGE, 0.0f);
        SetFloatValue(PLAYER_BLOCK_PERCENTAGE, 0.0f);

        // Dodge percentage
        SetFloatValue(PLAYER_DODGE_PERCENTAGE, 0.0f);

        // set armor (resistance 0) to original value (create_agility*2)
        SetArmor(int(m_createStats[STAT_AGILITY] * 2));
        SetResistanceBuffMods(SpellSchools(0), true, 0.0f);
        SetResistanceBuffMods(SpellSchools(0), false, 0.0f);
        // set other resistance to original value (0)
        for (int i = 1; i < MAX_SPELL_SCHOOL; ++i)
        {
            SetResistance(SpellSchools(i), 0);
            SetResistanceBuffMods(SpellSchools(i), true, 0.0f);
            SetResistanceBuffMods(SpellSchools(i), false, 0.0f);
        }

        //[-ZERO]    SetUInt32Value(PLAYER_FIELD_MOD_TARGET_RESISTANCE,0);
        //[-ZERO]    SetUInt32Value(PLAYER_FIELD_MOD_TARGET_PHYSICAL_RESISTANCE,0);
        for (int i = 0; i < MAX_SPELL_SCHOOL; ++i)
        {
            SetUint32Value(UNIT_FIELD_POWER_COST_MODIFIER + i, 0);
            SetFloatValue(UNIT_FIELD_POWER_COST_MULTIPLIER + i, 0.0f);
        }
        // Init data for form but skip reapply item mods for form
        InitDataForForm(reapplyMods);

#if 0
        // save new stats
        for (int i = POWER_MANA; i < MAX_POWERS; ++i)
        {
            SetMaxPower(Powers(i), GetCreatePowers(Powers(i)));
        }

        SetMaxHealth(classInfo.basehealth);                     // stamina bonus will applied later

        // cleanup mounted state (it will set correctly at aura loading if player saved at mount.
        SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, 0);

        // cleanup unit flags (will be re-applied if need at aura load).
        RemoveFlag(UNIT_FIELD_FLAGS,
            UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_CLIENT_CONTROL_LOST | UNIT_FLAG_NOT_ATTACKABLE_1 |
            UNIT_FLAG_OOC_NOT_ATTACKABLE | UNIT_FLAG_PASSIVE | UNIT_FLAG_LOOTING |
            UNIT_FLAG_PET_IN_COMBAT | UNIT_FLAG_SILENCED | UNIT_FLAG_PACIFIED |
            UNIT_FLAG_STUNNED | UNIT_FLAG_IN_COMBAT | UNIT_FLAG_DISARMED |
            UNIT_FLAG_CONFUSED | UNIT_FLAG_FLEEING | UNIT_FLAG_NOT_SELECTABLE |
            UNIT_FLAG_SKINNABLE | UNIT_FLAG_TAXI_FLIGHT);
        SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE);    // must be set

        // cleanup player flags (will be re-applied if need at aura load), to avoid have ghost flag without ghost aura, for example.
        RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_AFK | PLAYER_FLAGS_DND | PLAYER_FLAGS_GM | PLAYER_FLAGS_GHOST | PLAYER_FLAGS_FFA_PVP);

        // one form stealth modified bytes
        RemoveByteFlag(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_ALL);

        // restore if need some important flags
        SetUInt32Value(PLAYER_FIELD_BYTES2, 0);                 // flags empty by default

        if (reapplyMods)                                        // reapply stats values only on .reset stats (level) command
        {
            _ApplyAllStatBonuses();
        }

        // set current level health and mana/energy to maximum after applying all mods.
        SetHealth(GetMaxHealth());
        SetPower(POWER_MANA, GetMaxPower(POWER_MANA));
        SetPower(POWER_ENERGY, GetMaxPower(POWER_ENERGY));
        if (GetPower(POWER_RAGE) > GetMaxPower(POWER_RAGE))
        {
            SetPower(POWER_RAGE, GetMaxPower(POWER_RAGE));
        }
        SetPower(POWER_FOCUS, 0);
        SetPower(POWER_HAPPINESS, 0);

        // update level to hunter/summon pet
        if (Pet* pet = GetPet())
        {
            pet->SynchronizeLevelWithOwner();
        }
#endif
    }

    void Player::SetRestBonus(float rest_bonus_new)
    {
#if 0
        // Prevent resting on max level
        if (getLevel() >= sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL))
        {
            rest_bonus_new = 0;
        }

        if (rest_bonus_new < 0)
        {
            rest_bonus_new = 0;
        }

        float rest_bonus_max = (float)GetUInt32Value(PLAYER_NEXT_LEVEL_XP) * 1.5f / 2.0f;

        if (rest_bonus_new > rest_bonus_max)
        {
            m_rest_bonus = rest_bonus_max;
        }
        else
        {
            m_rest_bonus = rest_bonus_new;
        }

        // update data for client
        if (m_rest_bonus > 10)
        {
            SetByteValue(PLAYER_BYTES_2, 3, REST_STATE_RESTED);
        }
        else if (m_rest_bonus <= 1)
        {
            SetByteValue(PLAYER_BYTES_2, 3, REST_STATE_NORMAL);
        }

        // RestTickUpdate
        SetUInt32Value(PLAYER_REST_STATE_EXPERIENCE, uint32(m_rest_bonus));
#endif
    }

    float Player::ComputeRest(time_t timePassed, bool offline /*= false*/, bool inRestPlace /*= false*/)
    {
#if 1
        return 0.0f;
#else
        // Every 8h in resting zone we gain a bubble
        // A bubble is 5% of the total xp so there is 20 bubbles
        // So we gain (total XP/20 every 8h) (8h = 288800 sec)
        // (TotalXP/20)/28800; simplified to (TotalXP/576000) per second
        // Client automatically double the value sent so we have to divide it by 2
        // So final formula (TotalXP/1152000)
        float bonus = timePassed * (GetUInt32Value(PLAYER_NEXT_LEVEL_XP) / 1152000.0f); // Get the gained rest xp for given second
        if (!offline)
        {
            bonus *= sWorld.getConfig(CONFIG_FLOAT_RATE_REST_INGAME);                   // Apply the custom setting
        }
        else
        {
            if (inRestPlace)
            {
                bonus *= sWorld.getConfig(CONFIG_FLOAT_RATE_REST_OFFLINE_IN_TAVERN_OR_CITY);
            }
            else
            {
                bonus *= sWorld.getConfig(CONFIG_FLOAT_RATE_REST_OFFLINE_IN_WILDERNESS) / 4.0f; // bonus is reduced by 4 when not in rest place
            }
        }
        return bonus;
#endif
    }

    void Player::_LoadSkills(std::shared_ptr<cftf::mysql::DataSet> ds)
    {
        //                                                           0      1      2
        // SetPQuery(PLAYER_LOGIN_QUERY_LOADSKILLS,          "SELECT `skill`, `value`, `max` FROM `character_skills` WHERE `guid` = '%u'", GUID_LOPART(m_guid));

#if 0
        unsigned intcount = 0;
        if (result)
        {
            do
            {
                Field* fields = result->Fetch();

                unsigned shortskill = fields[0].GetUInt16();
                unsigned shortvalue = fields[1].GetUInt16();
                unsigned shortmax = fields[2].GetUInt16();

                SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(skill);
                if (!pSkill)
                {
                    sLog.outError("Character %u has skill %u that does not exist.", GetGUIDLow(), skill);
                    continue;
                }

                // set fixed skill ranges
                switch (GetSkillRangeType(pSkill, false))
                {
                case SKILL_RANGE_LANGUAGE:                  // 300..300
                    value = max = 300;
                    break;
                case SKILL_RANGE_MONO:                      // 1..1, grey monolite bar
                    value = max = 1;
                    break;
                case SKILL_RANGE_LEVEL:
                    max = GetMaxSkillValueForLevel();       // max value can be wrong for the actual level
                    break;
                default:
                    break;
                }

                if (value == 0)
                {
                    sLog.outError("Character %u has skill %u with value 0. Will be deleted.", GetGUIDLow(), skill);
                    CharacterDatabase.PExecute("DELETE FROM `character_skills` WHERE `guid` = '%u' AND `skill` = '%u' ", GetGUIDLow(), skill);
                    continue;
                }

                SetUInt32Value(PLAYER_SKILL_INDEX(count), MAKE_PAIR32(skill, 0));
                SetUInt32Value(PLAYER_SKILL_VALUE_INDEX(count), MAKE_SKILL_VALUE(value, max));
                SetUInt32Value(PLAYER_SKILL_BONUS_INDEX(count), 0);

                mSkillStatus.insert(SkillStatusMap::value_type(skill, SkillStatusData(count, SKILL_UNCHANGED)));

                learnSkillRewardedSpells(skill, value);

                ++count;

                if (count >= PLAYER_MAX_SKILLS)                 // client limit
                {
                    sLog.outError("Character %u has more than %u skills.", GetGUIDLow(), PLAYER_MAX_SKILLS);
                    break;
                }
            } while (result->NextRow());
            delete result;
        }

        for (; count < PLAYER_MAX_SKILLS; ++count)
        {
            SetUInt32Value(PLAYER_SKILL_INDEX(count), 0);
            SetUInt32Value(PLAYER_SKILL_VALUE_INDEX(count), 0);
            SetUInt32Value(PLAYER_SKILL_BONUS_INDEX(count), 0);
        }
#endif
    }

    void Player::_LoadMails(std::shared_ptr<cftf::mysql::DataSet> ds)
    {
#if 0
        m_mail.clear();
        //        0  1           2      3        4       5    6           7            8     9   10      11         12             13
        //"SELECT id,messageType,sender,receiver,subject,body,expire_time,deliver_time,money,cod,checked,stationery,mailTemplateId,has_items FROM mail WHERE receiver = '%u' ORDER BY id DESC", GetGUIDLow()
        if (!result)
        {
            return;
        }

        do
        {
            Field* fields = result->Fetch();
            Mail* m = new Mail;
            m->messageID = fields[0].GetUInt32();
            m->messageType = fields[1].GetUInt8();
            m->sender = fields[2].GetUInt32();
            m->receiverGuid = ObjectGuid(HIGHGUID_PLAYER, fields[3].GetUInt32());
            m->subject = fields[4].GetCppString();
            m->body = fields[5].GetCppString();
            m->expire_time = (time_t)fields[6].GetUInt64();
            m->deliver_time = (time_t)fields[7].GetUInt64();
            m->money = fields[8].GetUInt32();
            m->COD = fields[9].GetUInt32();
            m->checked = fields[10].GetUInt32();
            m->stationery = fields[11].GetUInt8();
            m->mailTemplateId = fields[12].GetInt16();
            m->has_items = fields[13].GetBool();                // true, if mail have items or mail have template and items generated (maybe none)

            if (m->mailTemplateId && !sMailTemplateStore.LookupEntry(m->mailTemplateId))
            {
                sLog.outError("Player::_LoadMail - Mail (%u) have nonexistent MailTemplateId (%u), remove at load", m->messageID, m->mailTemplateId);
                m->mailTemplateId = 0;
            }

            m->state = MAIL_STATE_UNCHANGED;

            m_mail.push_back(m);

            if (m->mailTemplateId && !m->has_items)
            {
                m->prepareTemplateItems(this);
            }
        } while (result->NextRow());
        delete result;
#endif
    }

    void Player::_LoadMailedItems(std::shared_ptr<cftf::mysql::DataSet> ds)
    {
#if 0
        // data needs to be at first place for Item::LoadFromDB
            //         0     1     2        3          4
            // "SELECT data, text, mail_id, item_guid, item_template FROM mail_items JOIN item_instance ON item_guid = guid WHERE receiver = '%u'", GUID_LOPART(m_guid)
        if (!result)
        {
            return;
        }

        do
        {
            Field* fields = result->Fetch();
            unsigned intmail_id = fields[1].GetUInt32();
            unsigned intitem_guid_low = fields[2].GetUInt32();
            unsigned intitem_template = fields[3].GetUInt32();

            Mail* mail = GetMail(mail_id);
            if (!mail)
            {
                continue;
            }
            mail->AddItem(item_guid_low, item_template);

            ItemPrototype const* proto = ObjectMgr::GetItemPrototype(item_template);

            if (!proto)
            {
                sLog.outError("Player %u has unknown item_template (ProtoType) in mailed items(GUID: %u template: %u) in mail (%u), deleted.", GetGUIDLow(), item_guid_low, item_template, mail->messageID);
                CharacterDatabase.PExecute("DELETE FROM `mail_items` WHERE `item_guid` = '%u'", item_guid_low);
                CharacterDatabase.PExecute("DELETE FROM `item_instance` WHERE `guid` = '%u'", item_guid_low);
                continue;
            }

            Item* item = NewItemOrBag(proto);

            if (!item->LoadFromDB(item_guid_low, fields, GetObjectGuid()))
            {
                sLog.outError("Player::_LoadMailedItems - Item in mail (%u) doesn't exist !!!! - item guid: %u, deleted from mail", mail->messageID, item_guid_low);
                CharacterDatabase.PExecute("DELETE FROM `mail_items` WHERE `item_guid` = '%u'", item_guid_low);
                item->FSetState(ITEM_REMOVED);
                item->SaveToDB();                               // it also deletes item object !
                continue;
            }

            AddMItem(item);
        } while (result->NextRow());

        delete result;
#endif
    }

    void Player::UpdateNextMailTimeAndUnreads()
    {
#if 0
        // calculate next delivery time (min. from non-delivered mails
            // and recalculate unReadMail
        time_t cTime = time(NULL);
        m_nextMailDelivereTime = 0;
        unReadMails = 0;
        for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
        {
            if ((*itr)->deliver_time > cTime)
            {
                if (!m_nextMailDelivereTime || m_nextMailDelivereTime > (*itr)->deliver_time)
                {
                    m_nextMailDelivereTime = (*itr)->deliver_time;
                }
            }
            else if (((*itr)->checked & MAIL_CHECK_MASK_READ) == 0)
            {
                ++unReadMails;
            }
        }
#endif
    }

    void Player::_LoadAuras(std::shared_ptr<cftf::mysql::DataSet> ds, unsigned int timediff)
    {
#if 0
        // RemoveAllAuras(); -- some spells casted before aura load, for example in LoadSkills, aura list explicitly cleaned early

            // all aura related fields
        for (int i = UNIT_FIELD_AURA; i <= UNIT_FIELD_AURASTATE; ++i)
        {
            SetUInt32Value(i, 0);
        }

        // QueryResult *result = CharacterDatabase.PQuery("SELECT `caster_guid`,`item_guid`,`spell`,`stackcount`,`remaincharges`,`basepoints0`,`basepoints1`,`basepoints2`,`periodictime0`,`periodictime1`,`periodictime2`,`maxduration`,`remaintime`,`effIndexMask` FROM `character_aura` WHERE `guid` = '%u'",GetGUIDLow());

        if (result)
        {
            do
            {
                Field* fields = result->Fetch();
                ObjectGuid caster_guid = ObjectGuid(fields[0].GetUInt64());
                unsigned intitem_lowguid = fields[1].GetUInt32();
                unsigned intspellid = fields[2].GetUInt32();
                unsigned intstackcount = fields[3].GetUInt32();
                unsigned intremaincharges = fields[4].GetUInt32();
                int32  damage[MAX_EFFECT_INDEX];
                unsigned intperiodicTime[MAX_EFFECT_INDEX];

                for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i)
                {
                    damage[i] = fields[i + 5].GetInt32();
                    periodicTime[i] = fields[i + 8].GetUInt32();
                }

                int32 maxduration = fields[11].GetInt32();
                int32 remaintime = fields[12].GetInt32();
                unsigned inteffIndexMask = fields[13].GetUInt32();

                SpellEntry const* spellproto = sSpellStore.LookupEntry(spellid);
                if (!spellproto)
                {
                    sLog.outError("Unknown spell (spellid %u), ignore.", spellid);
                    continue;
                }

                if (remaintime != -1 && !IsPositiveSpell(spellproto))
                {
                    if (remaintime / IN_MILLISECONDS <= int32(timediff))
                    {
                        continue;
                    }

                    remaintime -= timediff * IN_MILLISECONDS;
                }

                // prevent wrong values of remaincharges
                if (spellproto->procCharges == 0)
                {
                    remaincharges = 0;
                }

                if (!spellproto->StackAmount)
                {
                    stackcount = 1;
                }
                else if (spellproto->StackAmount < stackcount)
                {
                    stackcount = spellproto->StackAmount;
                }
                else if (!stackcount)
                {
                    stackcount = 1;
                }

                SpellAuraHolder* holder = CreateSpellAuraHolder(spellproto, this, NULL);
                holder->SetLoadedState(caster_guid, ObjectGuid(HIGHGUID_ITEM, item_lowguid), stackcount, remaincharges, maxduration, remaintime);

                for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i)
                {
                    if ((effIndexMask & (1 << i)) == 0)
                    {
                        continue;
                    }

                    Aura* aura = CreateAura(spellproto, SpellEffectIndex(i), NULL, holder, this);
                    if (!damage[i])
                    {
                        damage[i] = aura->GetModifier()->m_amount;
                    }

                    aura->SetLoadedState(damage[i], periodicTime[i]);
                    holder->AddAura(aura, SpellEffectIndex(i));
                }

                if (!holder->IsEmptyHolder())
                {
                    // reset stolen single target auras
                    if (caster_guid != GetObjectGuid() && holder->GetTrackedAuraType() == TRACK_AURA_TYPE_SINGLE_TARGET)
                    {
                        holder->SetTrackedAuraType(TRACK_AURA_TYPE_NOT_TRACKED);
                    }

                    AddSpellAuraHolder(holder);
                    DETAIL_LOG("Added auras from spellid %u", spellproto->Id);
                }
                else
                {
                    delete holder;
                }
            } while (result->NextRow());
            delete result;
        }

        if (getClass() == CLASS_WARRIOR && !HasAuraType(SPELL_AURA_MOD_SHAPESHIFT))
        {
            CastSpell(this, SPELL_ID_PASSIVE_BATTLE_STANCE, true);
        }
#endif
    }

    void Player::_LoadSpells(std::shared_ptr<cftf::mysql::DataSet> ds)
    {
#if 0
        // QueryResult *result = CharacterDatabase.PQuery("SELECT `spell`,`active`,`disabled` FROM `character_spell` WHERE `guid` = '%u'",GetGUIDLow());

        if (result)
        {
            do
            {
                Field* fields = result->Fetch();

                unsigned intspell_id = fields[0].GetUInt32();

                addSpell(spell_id, fields[1].GetBool(), false, false, fields[2].GetBool());
            } while (result->NextRow());

            delete result;
        }
#endif
    }

    void Player::InitTalentForLevel()
    {
        UpdateFreeTalentPoints();
    }

    void Player::UpdateFreeTalentPoints(bool resetIfNeed)
    {
#if 0
        unsigned intlevel = getLevel();
        // talents base at level diff ( talents = level - 9 but some can be used already)
        if (level < 10)
        {
            // Remove all talent points
            if (m_usedTalentCount > 0)                          // Free any used talents
            {
                if (resetIfNeed)
                {
                    resetTalents(true);
                }
                SetFreeTalentPoints(0);
            }
        }
        else
        {
            unsigned inttalentPointsForLevel = CalculateTalentsPoints();

            // if used more that have then reset
            if (m_usedTalentCount > talentPointsForLevel)
            {
                if (resetIfNeed && GetSession()->GetSecurity() < SEC_ADMINISTRATOR)
                {
                    resetTalents(true);
                }
                else
                {
                    SetFreeTalentPoints(0);
                }
            }
            // else update amount of free points
            else
            {
                SetFreeTalentPoints(talentPointsForLevel - m_usedTalentCount);
            }
        }
#endif
    }

    void Player::learnDefaultSpells()
    {
#if 0
        // learn default race/class spells
        PlayerInfo const* info = sObjectMgr.GetPlayerInfo(getRace(), getClass());
        for (PlayerCreateInfoSpells::const_iterator itr = info->spell.begin(); itr != info->spell.end(); ++itr)
        {
            unsigned inttspell = *itr;
            DEBUG_LOG("PLAYER (Class: %u Race: %u): Adding initial spell, id = %u", uint32(getClass()), uint32(getRace()), tspell);
            if (!IsInWorld())                                   // will send in INITIAL_SPELLS in list anyway at map add
            {
                addSpell(tspell, true, true, true, false);
            }
            else                                                // but send in normal spell in game learn case
            {
                learnSpell(tspell, true);
            }
        }
#endif
    }

    void Player::_LoadQuestStatus(std::shared_ptr<cftf::mysql::DataSet> ds)
    {
#if 0
        mQuestStatus.clear();

        unsigned intslot = 0;

        ////                                                       0        1         2           3           4        5            6            7            8            9             10            11            12
        // QueryResult *result = CharacterDatabase.PQuery("SELECT `quest`, `status`, `rewarded`, `explored`, `timer`, `mobcount1`, `mobcount2`, `mobcount3`, `mobcount4`, `itemcount1`, `itemcount2`, `itemcount3`, `itemcount4` FROM `character_queststatus` WHERE `guid` = '%u'", GetGUIDLow());

        if (result)
        {
            do
            {
                Field* fields = result->Fetch();

                unsigned intquest_id = fields[0].GetUInt32();
                // used to be new, no delete?
                Quest const* pQuest = sObjectMgr.GetQuestTemplate(quest_id);
                if (pQuest)
                {
                    // find or create
                    QuestStatusData& questStatusData = mQuestStatus[quest_id];

                    unsigned intqstatus = fields[1].GetUInt32();
                    if (qstatus < MAX_QUEST_STATUS)
                    {
                        questStatusData.m_status = QuestStatus(qstatus);
                    }
                    else
                    {
                        questStatusData.m_status = QUEST_STATUS_NONE;
                        sLog.outError("Player %s have invalid quest %d status (%d), replaced by QUEST_STATUS_NONE(0).", GetName(), quest_id, qstatus);
                    }

                    questStatusData.m_rewarded = (fields[2].GetUInt8() > 0);
                    questStatusData.m_explored = (fields[3].GetUInt8() > 0);

                    time_t quest_time = time_t(fields[4].GetUInt64());

                    if (pQuest->HasSpecialFlag(QUEST_SPECIAL_FLAG_TIMED) && !GetQuestRewardStatus(quest_id) && questStatusData.m_status != QUEST_STATUS_NONE)
                    {
                        AddTimedQuest(quest_id);

                        if (quest_time <= sWorld.GetGameTime())
                        {
                            questStatusData.m_timer = 1;
                        }
                        else
                        {
                            questStatusData.m_timer = uint32(quest_time - sWorld.GetGameTime()) * IN_MILLISECONDS;
                        }
                    }
                    else
                    {
                        quest_time = 0;
                    }

                    questStatusData.m_creatureOrGOcount[0] = fields[5].GetUInt32();
                    questStatusData.m_creatureOrGOcount[1] = fields[6].GetUInt32();
                    questStatusData.m_creatureOrGOcount[2] = fields[7].GetUInt32();
                    questStatusData.m_creatureOrGOcount[3] = fields[8].GetUInt32();
                    questStatusData.m_itemcount[0] = fields[9].GetUInt32();
                    questStatusData.m_itemcount[1] = fields[10].GetUInt32();
                    questStatusData.m_itemcount[2] = fields[11].GetUInt32();
                    questStatusData.m_itemcount[3] = fields[12].GetUInt32();

                    questStatusData.uState = QUEST_UNCHANGED;

                    // add to quest log
                    if (slot < MAX_QUEST_LOG_SIZE &&
                        ((questStatusData.m_status == QUEST_STATUS_INCOMPLETE ||
                            questStatusData.m_status == QUEST_STATUS_COMPLETE ||
                            questStatusData.m_status == QUEST_STATUS_FAILED) &&
                            (!questStatusData.m_rewarded || pQuest->IsRepeatable())))
                    {
                        SetQuestSlot(slot, quest_id, uint32(quest_time));

                        if (questStatusData.m_explored)
                        {
                            SetQuestSlotState(slot, QUEST_STATE_COMPLETE);
                        }

                        if (questStatusData.m_status == QUEST_STATUS_COMPLETE)
                        {
                            SetQuestSlotState(slot, QUEST_STATE_COMPLETE);
                        }

                        if (questStatusData.m_status == QUEST_STATUS_FAILED)
                        {
                            SetQuestSlotState(slot, QUEST_STATE_FAIL);
                        }

                        for (unsigned char idx = 0; idx < QUEST_OBJECTIVES_COUNT; ++idx)
                            if (questStatusData.m_creatureOrGOcount[idx])
                            {
                                SetQuestSlotCounter(slot, idx, questStatusData.m_creatureOrGOcount[idx]);
                            }

                        ++slot;
                    }

                    if (questStatusData.m_rewarded)
                    {
                        // learn rewarded spell if unknown
                        learnQuestRewardedSpells(pQuest);
                    }

                    DEBUG_LOG("Quest status is {%u} for quest {%u} for player (GUID: %u)", questStatusData.m_status, quest_id, GetGUIDLow());
                }
            } while (result->NextRow());

            delete result;
        }

        // clear quest log tail
        for (unsigned shorti = slot; i < MAX_QUEST_LOG_SIZE; ++i)
        {
            SetQuestSlot(i, 0);
        }
#endif
    }

    void Player::_LoadInventory(std::shared_ptr<cftf::mysql::DataSet> ds, unsigned int timediff)
    {
#if 0
        // QueryResult *result = CharacterDatabase.PQuery("SELECT `data`,`bag`,`slot`,`item`,`item_template` FROM `character_inventory` JOIN `item_instance` ON `character_inventory`.`item` = `item_instance`.`guid` WHERE `character_inventory`.`guid` = '%u' ORDER BY `bag`,`slot`", GetGUIDLow());
        std::map<uint32, Bag*> bagMap;                          // fast guid lookup for bags
        // NOTE: the "order by `bag`" is important because it makes sure
        // the bagMap is filled before items in the bags are loaded
        // NOTE2: the "order by `slot`" is needed because mainhand weapons are (wrongly?)
        // expected to be equipped before offhand items (TODO: fixme)

        unsigned intzone = GetZoneId();

        if (result)
        {
            std::list<Item*> problematicItems;

            // prevent items from being added to the queue when stored
            m_itemUpdateQueueBlocked = true;
            do
            {
                Field* fields = result->Fetch();
                unsigned intbag_guid = fields[1].GetUInt32();
                unsigned char  slot = fields[2].GetUInt8();
                unsigned intitem_lowguid = fields[3].GetUInt32();
                unsigned intitem_id = fields[4].GetUInt32();

                ItemPrototype const* proto = ObjectMgr::GetItemPrototype(item_id);

                if (!proto)
                {
                    CharacterDatabase.PExecute("DELETE FROM `character_inventory` WHERE `item` = '%u'", item_lowguid);
                    CharacterDatabase.PExecute("DELETE FROM `item_instance` WHERE `guid` = '%u'", item_lowguid);
                    sLog.outError("Player::_LoadInventory: Player %s has an unknown item (id: #%u) in inventory, deleted.", GetName(), item_id);
                    continue;
                }

                Item* item = NewItemOrBag(proto);

                if (!item->LoadFromDB(item_lowguid, fields, GetObjectGuid()))
                {
                    sLog.outError("Player::_LoadInventory: Player %s has broken item (id: #%u) in inventory, deleted.", GetName(), item_id);
                    CharacterDatabase.PExecute("DELETE FROM `character_inventory` WHERE `item` = '%u'", item_lowguid);
                    item->FSetState(ITEM_REMOVED);
                    item->SaveToDB();                           // it also deletes item object !
                    continue;
                }

                // not allow have in alive state item limited to another map/zone
                if (IsAlive() && item->IsLimitedToAnotherMapOrZone(GetMapId(), zone))
                {
                    CharacterDatabase.PExecute("DELETE FROM `character_inventory` WHERE `item` = '%u'", item_lowguid);
                    item->FSetState(ITEM_REMOVED);
                    item->SaveToDB();                           // it also deletes item object !
                    continue;
                }

                // "Conjured items disappear if you are logged out for more than 15 minutes"
                if (timediff > 15 * MINUTE && (item->GetProto()->Flags & ITEM_FLAG_CONJURED))
                {
                    CharacterDatabase.PExecute("DELETE FROM `character_inventory` WHERE `item` = '%u'", item_lowguid);
                    item->FSetState(ITEM_REMOVED);
                    item->SaveToDB();                           // it also deletes item object !
                    continue;
                }

                bool success = true;

                // the item/bag is not in a bag
                if (!bag_guid)
                {
                    item->SetContainer(NULL);
                    item->SetSlot(slot);

                    if (IsInventoryPos(INVENTORY_SLOT_BAG_0, slot))
                    {
                        ItemPosCountVec dest;
                        if (CanStoreItem(INVENTORY_SLOT_BAG_0, slot, dest, item, false) == EQUIP_ERR_OK)
                        {
                            item = StoreItem(dest, item, true);
                        }
                        else
                        {
                            success = false;
                        }
                    }
                    else if (IsEquipmentPos(INVENTORY_SLOT_BAG_0, slot))
                    {
                        unsigned shortdest;
                        if (CanEquipItem(slot, dest, item, false, false) == EQUIP_ERR_OK)
                        {
                            QuickEquipItem(dest, item);
                        }
                        else
                        {
                            success = false;
                        }
                    }
                    else if (IsBankPos(INVENTORY_SLOT_BAG_0, slot))
                    {
                        ItemPosCountVec dest;
                        if (CanBankItem(INVENTORY_SLOT_BAG_0, slot, dest, item, false, false) == EQUIP_ERR_OK)
                        {
                            item = BankItem(dest, item, true);
                        }
                        else
                        {
                            success = false;
                        }
                    }

                    if (success)
                    {
                        // store bags that may contain items in them
                        if (item->IsBag() && IsBagPos(item->GetPos()))
                        {
                            bagMap[item_lowguid] = (Bag*)item;
                        }
                    }
                }
                // the item/bag in a bag
                else
                {
                    item->SetSlot(NULL_SLOT);
                    // the item is in a bag, find the bag
                    std::map<uint32, Bag*>::const_iterator itr = bagMap.find(bag_guid);
                    if (itr != bagMap.end() && slot < itr->second->GetBagSize())
                    {
                        ItemPosCountVec dest;
                        if (CanStoreItem(itr->second->GetSlot(), slot, dest, item, false) == EQUIP_ERR_OK)
                        {
                            item = StoreItem(dest, item, true);
                        }
                        else
                        {
                            success = false;
                        }
                    }
                    else
                    {
                        success = false;
                    }
                }

                // item's state may have changed after stored
                if (success)
                {
                    item->SetState(ITEM_UNCHANGED, this);

                    // restore container unchanged state also
                    if (item->GetContainer())
                    {
                        item->GetContainer()->SetState(ITEM_UNCHANGED, this);
                    }
                }
                else
                {
                    sLog.outError("Player::_LoadInventory: Player %s has item (GUID: %u Entry: %u) can't be loaded to inventory (Bag GUID: %u Slot: %u) by some reason, will send by mail.", GetName(), item_lowguid, item_id, bag_guid, slot);
                    CharacterDatabase.PExecute("DELETE FROM `character_inventory` WHERE `item` = '%u'", item_lowguid);
                    problematicItems.push_back(item);
                }
            } while (result->NextRow());

            delete result;
            m_itemUpdateQueueBlocked = false;

            // send by mail problematic items
            while (!problematicItems.empty())
            {
                std::string subject = "Item could not be loaded to inventory.";
                std::string content = GetSession()->GetMangosString(LANG_NOT_EQUIPPED_ITEM);
                // fill mail
                MailDraft draft(subject, "");
                draft.SetSubjectAndBody(subject, content);
                for (int i = 0; !problematicItems.empty() && i < MAX_MAIL_ITEMS; ++i)
                {
                    Item* item = problematicItems.front();
                    problematicItems.pop_front();

                    draft.AddItem(item);
                }

                draft.SendMailTo(this, MailSender(this, MAIL_STATIONERY_GM), MAIL_CHECK_MASK_COPIED);
            }
        }

        // if(IsAlive())
        _ApplyAllItemMods();
#endif
    }

    void Player::_LoadItemLoot(std::shared_ptr<cftf::mysql::DataSet> ds)
    {
#if 0
        // QueryResult *result = CharacterDatabase.PQuery("SELECT `guid`,`itemid`,`amount`,`property` FROM `item_loot` WHERE `guid` = '%u'", GetGUIDLow());

        if (result)
        {
            do
            {
                Field* fields = result->Fetch();
                unsigned intitem_guid = fields[0].GetUInt32();

                Item* item = GetItemByGuid(ObjectGuid(HIGHGUID_ITEM, item_guid));

                if (!item)
                {
                    CharacterDatabase.PExecute("DELETE FROM `item_loot` WHERE `guid` = '%u'", item_guid);
                    sLog.outError("Player::_LoadItemLoot: Player %s has loot for nonexistent item (GUID: %u) in `item_loot`, deleted.", GetName(), item_guid);
                    continue;
                }

                item->LoadLootFromDB(fields);
            } while (result->NextRow());

            delete result;
        }
#endif
    }

    void Player::UpdateItemDuration(unsigned int time, bool realtimeonly)
    {
#if 0
        if (m_itemDuration.empty())
        {
            return;
        }

        DEBUG_LOG("Player::UpdateItemDuration(%u,%u)", time, realtimeonly);

        for (ItemDurationList::const_iterator itr = m_itemDuration.begin(); itr != m_itemDuration.end();)
        {
            Item* item = *itr;
            ++itr;                                              // current element can be erased in UpdateDuration

            if ((realtimeonly && (item->GetProto()->ExtraFlags & ITEM_EXTRA_REAL_TIME_DURATION)) || !realtimeonly)
            {
                item->UpdateDuration(this, time);
            }
        }
#endif
    }

    void Player::_LoadActions(std::shared_ptr<cftf::mysql::DataSet> ds)
    {
#if 0
        m_actionButtons.clear();

        // QueryResult *result = CharacterDatabase.PQuery("SELECT `button`,`action`,`type` FROM `character_action` WHERE `guid` = '%u' ORDER BY `button`",GetGUIDLow());

        if (result)
        {
            do
            {
                Field* fields = result->Fetch();

                unsigned char button = fields[0].GetUInt8();
                unsigned intaction = fields[1].GetUInt32();
                unsigned char type = fields[2].GetUInt8();

                if (ActionButton* ab = addActionButton(button, action, type))
                {
                    ab->uState = ACTIONBUTTON_UNCHANGED;
                }
                else
                {
                    sLog.outError("  ...at loading, and will deleted in DB also");

                    // Will deleted in DB at next save (it can create data until save but marked as deleted)
                    m_actionButtons[button].uState = ACTIONBUTTON_DELETED;
                }
            } while (result->NextRow());

            delete result;
        }
#endif
    }

    void Player::_LoadSpellCooldowns(std::shared_ptr<cftf::mysql::DataSet> ds)
    {
        // some cooldowns can be already set at aura loading...

        // QueryResult *result = CharacterDatabase.PQuery("SELECT `spell`,`item`,`time` FROM `character_spell_cooldown` WHERE `guid` = '%u'",GetGUIDLow());

#if 0
        if (result)
        {
            time_t curTime = time(NULL);

            do
            {
                Field* fields = result->Fetch();

                unsigned intspell_id = fields[0].GetUInt32();
                unsigned intitem_id = fields[1].GetUInt32();
                time_t db_time = (time_t)fields[2].GetUInt64();

                if (!sSpellStore.LookupEntry(spell_id))
                {
                    sLog.outError("Player %u has unknown spell %u in `character_spell_cooldown`, skipping.", GetGUIDLow(), spell_id);
                    continue;
                }

                // skip outdated cooldown
                if (db_time <= curTime)
                {
                    continue;
                }

                AddSpellCooldown(spell_id, item_id, db_time);

                DEBUG_LOG("Player (GUID: %u) spell %u, item %u cooldown loaded (%u secs).", GetGUIDLow(), spell_id, item_id, uint32(db_time - curTime));
            } while (result->NextRow());

            delete result;
        }
#endif
    }

    bool Player::UpdateAllStats()
    {
#if 0
        for (int i = STAT_STRENGTH; i < MAX_STATS; ++i)
        {
            float value = GetTotalStatValue(Stats(i));
            SetStat(Stats(i), (int32)value);
        }

        UpdateAttackPowerAndDamage();
        UpdateAttackPowerAndDamage(true);
        UpdateArmor();
        UpdateMaxHealth();

        for (int i = POWER_MANA; i < MAX_POWERS; ++i)
        {
            UpdateMaxPower(Powers(i));
        }

        UpdateAllCritPercentages();
        UpdateAllSpellCritChances();
        UpdateDefenseBonusesMod();
        UpdateSpellDamageAndHealingBonus();
        UpdateManaRegen();
        for (int i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i)
        {
            UpdateResistances(i);
        }
#endif
        return true;
    }

    void Player::SendCorpseReclaimDelay(bool load)
    {
#if 0
        Corpse* corpse = GetCorpse();
        if (!corpse)
        {
            return;
        }

        unsigned intdelay;
        if (load)
        {
            if (corpse->GetGhostTime() > m_deathExpireTime)
            {
                return;
            }

            bool pvp = corpse->GetType() == CORPSE_RESURRECTABLE_PVP;

            unsigned intcount;
            if ((pvp && sWorld.getConfig(CONFIG_BOOL_DEATH_CORPSE_RECLAIM_DELAY_PVP)) ||
                (!pvp && sWorld.getConfig(CONFIG_BOOL_DEATH_CORPSE_RECLAIM_DELAY_PVE)))
            {
                count = uint32(m_deathExpireTime - corpse->GetGhostTime()) / DEATH_EXPIRE_STEP;
                if (count >= MAX_DEATH_COUNT)
                {
                    count = MAX_DEATH_COUNT - 1;
                }
            }
            else
            {
                count = 0;
            }

            time_t expected_time = corpse->GetGhostTime() + corpseReclaimDelay[count];

            time_t now = time(NULL);
            if (now >= expected_time)
            {
                return;
            }

            delay = uint32(expected_time - now);
        }
        else
        {
            delay = GetCorpseReclaimDelay(corpse->GetType() == CORPSE_RESURRECTABLE_PVP);
        }

        //! corpse reclaim delay 30 * 1000ms or longer at often deaths
        WorldPacket data(SMSG_CORPSE_RECLAIM_DELAY, 4);
        data << uint32(delay * IN_MILLISECONDS);
        GetSession()->SendPacket(&data);
#endif
    }

    /* Called by WorldSession::HandlePlayerLogin */
    void Player::SendInitialPacketsBeforeAddToMap()
    {
#if 0
        /* This packet seems useless...
         * TODO: Work out if we need SMSG_SET_REST_START */
        WorldPacket data(SMSG_SET_REST_START, 4);
        data << uint32(0);                                      // unknown, may be rest state time or experience
        GetSession()->SendPacket(&data);

        /* Send information about player's home binding */
        data.Initialize(SMSG_BINDPOINTUPDATE, 5 * 4);
        data << m_homebindX << m_homebindY << m_homebindZ;
        data << (uint32)m_homebindMapId;
        data << (uint32)m_homebindAreaId;
        GetSession()->SendPacket(&data);

        /* Tutorial data */
        GetSession()->SendTutorialsData();
        SendInitialSpells();
        SendInitialActionButtons();

        /* Send player reputations */
        m_reputationMgr.SendInitialReputations();

        /* Update player's honour information (does not send anything) */
        UpdateHonor();

        const float game_time = 0.01666667f; // Game speed

        data.Initialize(SMSG_LOGIN_SETTIMESPEED, 4 + 4);
        data << uint32(secsToTimeBitFields(sWorld.GetGameTime()));
        data << game_time; // Float is 4 bytes here
        GetSession()->SendPacket(&data);

        // Set fly flag if player is on a taxi to avoid falling to the ground
        if (IsTaxiFlying())
        {
            m_movementInfo.AddMovementFlag(MOVEFLAG_FLYING);
        }

        /* Finally, set the player as the active mover */
        SetMover(this);
#endif
    }

    void Player::SendCinematicStart(unsigned int CinematicSequenceId)
    {
        auto msg = std::make_shared<cs::SMTrigggerClinematic>();
        msg->CinematicSequenceId = CinematicSequenceId;
        SendDirectMessage(msg);
    }

    AreaLockStatus Player::GetAreaTriggerLockStatus(mt::AreaTrigger const* at, unsigned int& miscRequirement)
    {
        miscRequirement = 0;

#if 0
        if (!at)
        {
            return AREA_LOCKSTATUS_UNKNOWN_ERROR;
        }

        MapEntry const* mapEntry = sMapStore.LookupEntry(at->target_mapId);
        if (!mapEntry)
        {
            return AREA_LOCKSTATUS_UNKNOWN_ERROR;
        }

        // Gamemaster can always enter
        if (isGameMaster())
        {
            return AREA_LOCKSTATUS_OK;
        }

        // Raid Requirements
        if (mapEntry->IsRaid() && !sWorld.getConfig(CONFIG_BOOL_INSTANCE_IGNORE_RAID))
            if (!GetGroup() || !GetGroup()->isRaidGroup())
            {
                return AREA_LOCKSTATUS_RAID_LOCKED;
            }

        if (at->condition) //condition validity is checked at startup
        {
            ConditionEntry fault;
            if (!sObjectMgr.IsPlayerMeetToCondition(at->condition, this, GetMap(), NULL, CONDITION_AREA_TRIGGER, &fault))
            {
                switch (fault.type)
                {
                case CONDITION_LEVEL:
                {
                    if (sWorld.getConfig(CONFIG_BOOL_INSTANCE_IGNORE_LEVEL))
                    {
                        break;
                    }
                    else
                    {
                        miscRequirement = fault.param1;
                        switch (fault.param2)
                        {
                        case 0: { return AREA_LOCKSTATUS_LEVEL_NOT_EQUAL; }
                        case 1: { return AREA_LOCKSTATUS_LEVEL_TOO_LOW; }
                        case 2: { return AREA_LOCKSTATUS_LEVEL_TOO_HIGH; }
                        }
                    }
                }

                case CONDITION_ITEM:
                {
                    miscRequirement = fault.param1;
                    return AREA_LOCKSTATUS_MISSING_ITEM;
                }

                case CONDITION_QUESTREWARDED:
                {
                    miscRequirement = fault.param1;
                    return AREA_LOCKSTATUS_QUEST_NOT_COMPLETED;
                }

                case CONDITION_TEAM:
                {
                    miscRequirement = fault.param1;
                    return AREA_LOCKSTATUS_WRONG_TEAM;
                }

                case CONDITION_PVP_RANK:
                {
                    miscRequirement = fault.param1;
                    return AREA_LOCKSTATUS_PVP_RANK;
                }

                default:
                    return AREA_LOCKSTATUS_UNKNOWN_ERROR;
                }
            }
        }

        // If the map is not created, assume it is possible to enter it.
        DungeonPersistentState* state = GetBoundInstanceSaveForSelfOrGroup(at->target_mapId);
        Map* map = sMapMgr.FindMap(at->target_mapId, state ? state->GetInstanceId() : 0);

        // Map's state check
        if (map && map->IsDungeon())
        {
            // can not enter if the instance is full (player cap), GMs don't count
            if (((DungeonMap*)map)->GetPlayersCountExceptGMs() >= ((DungeonMap*)map)->GetMaxPlayers())
            {
                return AREA_LOCKSTATUS_INSTANCE_IS_FULL;
            }

            // In Combat check
            if (map && map->GetInstanceData() && map->GetInstanceData()->IsEncounterInProgress())
            {
                return AREA_LOCKSTATUS_ZONE_IN_COMBAT;
            }

            // Bind Checks
            InstancePlayerBind* pBind = GetBoundInstance(at->target_mapId);
            if (pBind && pBind->perm && pBind->state != state)
            {
                return AREA_LOCKSTATUS_HAS_BIND;
            }
            if (pBind && pBind->perm && pBind->state != map->GetPersistentState())
            {
                return AREA_LOCKSTATUS_HAS_BIND;
            }
        }
#endif
        return AREA_LOCKSTATUS_OK;
    }

    bool Player::TeleportTo(unsigned int mapid, float x, float y, float z, float orientation, unsigned int options /*=0*/, bool allowNoDelay /*=false*/)
    {
#if 1
        return true;
#else
        if (!MapManager::IsValidMapCoord(mapid, x, y, z, orientation))
        {
            sLog.outError("TeleportTo: invalid map %d or absent instance template.", mapid);
            return false;
        }

        MapEntry const* mEntry = sMapStore.LookupEntry(mapid);  // Validity checked in IsValidMapCoord

        if (!isGameMaster() && DisableMgr::IsDisabledFor(DISABLE_TYPE_MAP, mapid, this))
        {
            sLog.outDebug("Player (GUID: %u, name: %s) tried to enter a forbidden map %u", GetGUIDLow(), GetName(), mapid);
            SendTransferAbortedByLockStatus(mEntry, nullptr, AREA_LOCKSTATUS_NOT_ALLOWED);
            return false;
        }

        // preparing unsummon pet if lost (we must get pet before teleportation or will not find it later)
        Pet* pet = GetPet();

        // don't let enter battlegrounds without assigned battleground id (for example through areatrigger)...
        // don't let gm level > 1 either
        if (!InBattleGround() && mEntry->IsBattleGround())
        {
            return false;
        }

        // Check requirements for teleport
        if (!IsAlive() && mEntry->IsDungeon())    // rare case of teleporting the player into an instance with no areatrigger participation
        {
            ResurrectPlayer(0.5f);
            SpawnCorpseBones();
        }

        // if we were on a transport, leave
        if (!(options & TELE_TO_NOT_LEAVE_TRANSPORT) && m_transport)
        {
            m_transport->RemovePassenger(this);
            m_transport = NULL;
            m_movementInfo.ClearTransportData();
        }

        // The player was ported to another map and looses the duel immediately.
        // We have to perform this check before the teleport, otherwise the
        // ObjectAccessor won't find the flag.
        if (duel && GetMapId() != mapid)
            if (GetMap()->GetGameObject(GetGuidValue(PLAYER_DUEL_ARBITER)))
            {
                DuelComplete(DUEL_FLED);
            }

        // reset movement flags at teleport, because player will continue move with these flags after teleport
        m_movementInfo.SetMovementFlags(MOVEFLAG_NONE);
        DisableSpline();

        if ((GetMapId() == mapid) && (!m_transport))            // TODO the !m_transport might have unexpected effects when teleporting from transport to other place on same map
        {
            // lets reset far teleport flag if it wasn't reset during chained teleports
            SetSemaphoreTeleportFar(false);
            // setup delayed teleport flag
            // if teleport spell is casted in Unit::Update() func
            // then we need to delay it until update process will be finished
            if (!allowNoDelay)
            {
                if (SetDelayedTeleportFlagIfCan())
                {
                    SetSemaphoreTeleportNear(true);
                    // lets save teleport destination for player
                    m_teleport_dest = WorldLocation(mapid, x, y, z, orientation);
                    m_teleport_options = options;
                    return true;
                }
            }

            if (!(options & TELE_TO_NOT_UNSUMMON_PET))
            {
                // same map, only remove pet if out of range for new position
                if (pet)
                {
                    if (!pet->IsWithinDist3d(x, y, z, GetMap()->GetVisibilityDistance()))
                    {
                        if (pet->IsAlive())
                        {
                            UnsummonPetTemporaryIfAny();
                        }
                        else
                        {
                            pet->Unsummon(PET_SAVE_NOT_IN_SLOT);
                            pet = GetPet();
                        }
                    }
                }
            }

            if (!(options & TELE_TO_NOT_LEAVE_COMBAT))
            {
                CombatStop();
            }

            // this will be used instead of the current location in SaveToDB
            m_teleport_dest = WorldLocation(mapid, x, y, z, orientation);
            SetFallInformation(0, z);

            // code for finish transfer called in WorldSession::HandleMovementOpcodes()
            // at client packet MSG_MOVE_TELEPORT_ACK
            SetSemaphoreTeleportNear(true);
            // near teleport, triggering send MSG_MOVE_TELEPORT_ACK from client at landing
            if (!GetSession()->PlayerLogout())
            {
                WorldPacket data;
                BuildTeleportAckMsg(data, x, y, z, orientation);
                GetSession()->SendPacket(&data);
            }
        }
        else
        {
            // far teleport to another map
            Map* oldmap = IsInWorld() ? GetMap() : NULL;
            // check if we can enter before stopping combat / removing pet / totems / interrupting spells

            // If the map is not created, assume it is possible to enter it.
            // It will be created in the WorldPortAck.
            DungeonPersistentState* state = GetBoundInstanceSaveForSelfOrGroup(mapid);
            Map* map = sMapMgr.FindMap(mapid, state ? state->GetInstanceId() : 0);
            if (!map || map->CanEnter(this))
            {
                // lets reset near teleport flag if it wasn't reset during chained teleports
                SetSemaphoreTeleportNear(false);
                // setup delayed teleport flag
                // if teleport spell is casted in Unit::Update() func
                // then we need to delay it until update process will be finished
                if (!allowNoDelay)
                {
                    if (SetDelayedTeleportFlagIfCan())
                    {
                        SetSemaphoreTeleportFar(true);
                        // lets save teleport destination for player
                        m_teleport_dest = WorldLocation(mapid, x, y, z, orientation);
                        m_teleport_options = options;
                        return true;
                    }
                }

                SetSelectionGuid(ObjectGuid());

                CombatStop();

                ResetContestedPvP();

                // remove player from battleground on far teleport (when changing maps)
                if (BattleGround const* bg = GetBattleGround())
                {
                    // Note: at battleground join battleground id set before teleport
                    // and we already will found "current" battleground
                    // just need check that this is targeted map or leave
                    if (bg->GetMapId() != mapid)
                    {
                        LeaveBattleground(false); // don't teleport to entry point
                    }
                }

                // remove pet on map change
                if (pet)
                {
                    if (pet->IsAlive())
                    {
                        UnsummonPetTemporaryIfAny();
                    }
                    else
                    {
                        pet->Unsummon(PET_SAVE_NOT_IN_SLOT);
                        pet = GetPet();
                    }
                }
                // remove all dyn objects
                RemoveAllDynObjects();

                // stop spellcasting
                // not attempt interrupt teleportation spell at caster teleport
                if (!(options & TELE_TO_SPELL))
                    if (IsNonMeleeSpellCasted(true))
                    {
                        InterruptNonMeleeSpells(true);
                    }

                // remove auras before removing from map...
                RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_CHANGE_MAP | AURA_INTERRUPT_FLAG_MOVE | AURA_INTERRUPT_FLAG_TURNING);

                if (!GetSession()->PlayerLogout())
                {
                    // send transfer packet to display load screen
                    WorldPacket data(SMSG_TRANSFER_PENDING, (4 + 4 + 4));
                    data << uint32(mapid);
                    if (m_transport)
                    {
                        data << uint32(m_transport->GetEntry());
                        data << uint32(GetMapId());
                    }
                    GetSession()->SendPacket(&data);
                }

                // remove from old map now
                if (oldmap)
                {
                    oldmap->Remove(this, false);
                }

                // new final coordinates
                float final_x = x;
                float final_y = y;
                float final_z = z;
                float final_o = orientation;

                Position const* transportPosition = m_movementInfo.GetTransportPos();

                if (m_transport)
                {
                    final_x += transportPosition->x;
                    final_y += transportPosition->y;
                    final_z += transportPosition->z;
                    final_o += transportPosition->o;
                }

                m_teleport_dest = WorldLocation(mapid, final_x, final_y, final_z, final_o);
                SetFallInformation(0, final_z);
                // if the player is saved before worldport ack (at logout for example)
                // this will be used instead of the current location in SaveToDB

                // move packet sent by client always after far teleport
                // code for finish transfer to new map called in WorldSession::HandleMoveWorldportAckOpcode at client packet
                SetSemaphoreTeleportFar(true);

                if (!GetSession()->PlayerLogout())
                {
                    // transfer finished, inform client to start load
                    WorldPacket data(SMSG_NEW_WORLD, (20));
                    data << uint32(mapid);
                    if (m_transport)
                    {
                        data << float(transportPosition->x);
                        data << float(transportPosition->y);
                        data << float(transportPosition->z);
                        data << float(transportPosition->o);
                    }
                    else
                    {
                        data << float(final_x);
                        data << float(final_y);
                        data << float(final_z);
                        data << float(final_o);
                    }

                    GetSession()->SendPacket(&data);
                    SendSavedInstances();
                }
            }
            else                                                // !map->CanEnter(this)
            {
                return false;
            }
        }
        return true;
#endif
    }

}